include/oox/drawingml/diagram/diagramhelper_oox.hxx | 16 - include/oox/export/drawingml.hxx | 1 include/svx/diagram/DiagramHelper_svx.hxx | 3 oox/source/drawingml/diagram/datamodel_oox.cxx | 92 +++++-- oox/source/drawingml/diagram/datamodel_oox.hxx | 3 oox/source/drawingml/diagram/diagram.cxx | 142 +++++----- oox/source/drawingml/diagram/diagram.hxx | 5 oox/source/drawingml/diagram/diagramhelper_oox.cxx | 18 + oox/source/drawingml/graphicshapecontext.cxx | 6 oox/source/export/drawingml.cxx | 261 +++++++++++--------- sd/source/filter/eppt/pptx-epptooxml.cxx | 18 - svx/source/diagram/datamodel_svx.cxx | 2 12 files changed, 352 insertions(+), 215 deletions(-)
New commits: commit f9387fcc32267624c47e57a1ca9ad9394962b84c Author: Armin Le Grand (collabora) <[email protected]> AuthorDate: Thu Feb 5 14:28:07 2026 +0100 Commit: Armin Le Grand <[email protected]> CommitDate: Thu Feb 5 20:34:06 2026 +0100 SmartArt: Changed DomTree creation to be done at export time I digged into why when using graphics and/or links the current method to re-create missing DataDoms (for OOXData and OOXDrawing) did not work. Reason is that :blip and :hlinkClick need to get embedded in the MSO way. Only way to do this is to use the original target for writing with all that directory structures included, no way to keep that using a temp file for writing. The alternative would be to use a target with supporting that directory structure, but then would need to move all kind of stuff to real target. Thus I had to adapt all DomTree creation to be done at export time. That creates the correct exports, but I will no longer have a simple entry point for just creating missing DomTrees. Also stumbled over 'Relations' and their extensive use in MSO file formats & exports, learned about how that stuff is partially automated - I was really surprised to see code that queries a PropertySet from a XOutputStream - but hey, use what is there... Also learned that :hlinkClick and :blip in MSO XML formats for Diagram, both, the OOXData and OOXDiagaram, need to write additional Namespace info in form of xmlns:r and pointing to relationship schema - for *each* such entry. As until finished this all is secured by EnvVar ACTIVATE_RECREATE_DIAGRAM_DATADOMS not not break stuff until all looks stable. With this changes it now works to export a changed Diagram and reload and it stays a diagram. Still quirks, but now supports fills for BG/Shapes/Nodes and Links for Text in Nodes. Had to adapt to UnitTest testfdo79822 where a TestFile created in ms word 2007 is used. It has *no* initial DrawingDom. This might be corrected in the future, but for now do what previous versons did: Do not create a DrawingDom from scratch (we can now...) Change-Id: I0cb326ab5a340acfd7bb4d7f511178ea68bb15f3 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198756 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 b3c9cb3f3cd2..046e4e885ede 100644 --- a/include/oox/drawingml/diagram/diagramhelper_oox.hxx +++ b/include/oox/drawingml/diagram/diagramhelper_oox.hxx @@ -55,6 +55,9 @@ class DiagramHelper_oox final : public svx::diagram::DiagramHelper_svx OUString msNewNodeId; OUString msNewNodeText; + // flag to remember if initial import had a DrawingDom at all + bool mbInitiallyNoDrawingDom; + bool hasDiagramData() const; static void moveDiagramModelDataFromOldToNewXShape( @@ -100,9 +103,16 @@ public: void setOOXDomValue(svx::diagram::DomMapFlag aDomMapFlag, const css::uno::Any& rValue); virtual css::uno::Any getOOXDomValue(svx::diagram::DomMapFlag aDomMapFlag) const override; - // check if mandatory DiagramDomS exist (or can be created) - bool checkMinimalDataDoms() const; - void tryToCreateMissingDataDoms(DrawingML& rOriginalDrawingML); + // check if mandatory DiagramDomS exist and/or were not touched + virtual bool checkMinimalDataDoms() const override; + + // helpers to write some specific DiagramDoms + void writeDiagramOOXData(DrawingML& rOriginalDrawingML, css::uno::Reference<css::io::XOutputStream>& xOutputStream, std::u16string_view rDrawingRelId) const; + void writeDiagramOOXDrawing(DrawingML& rOriginalDrawingML, css::uno::Reference<css::io::XOutputStream>& xOutputStream) const; + + // flag to remember if initial import had a DrawingDom at all + void setInitiallyNoDrawingDom(bool bNew) { mbInitiallyNoDrawingDom = bNew; } + bool getInitiallyNoDrawingDom() const { return mbInitiallyNoDrawingDom; } }; } diff --git a/include/oox/export/drawingml.hxx b/include/oox/export/drawingml.hxx index e0c2d47dc920..a875946d70d4 100644 --- a/include/oox/export/drawingml.hxx +++ b/include/oox/export/drawingml.hxx @@ -521,7 +521,6 @@ public: OString WriteWdpPicture( const OUString& rFileId, const css::uno::Sequence< sal_Int8 >& rPictureData ); // Diagram helpers - OOX_DLLPUBLIC bool PrepareToWriteAsDiagram(const css::uno::Reference<css::drawing::XShape>& rXRootShape); OOX_DLLPUBLIC void WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rXShape, sal_Int32 nDiagramId, sal_Int32 nShapeId = -1); void writeDiagramImageRels(const css::uno::Sequence<css::uno::Sequence<css::uno::Any>>& xRelSeq, diff --git a/include/svx/diagram/DiagramHelper_svx.hxx b/include/svx/diagram/DiagramHelper_svx.hxx index 517bc77699d5..583964d7ac40 100644 --- a/include/svx/diagram/DiagramHelper_svx.hxx +++ b/include/svx/diagram/DiagramHelper_svx.hxx @@ -122,6 +122,9 @@ public: // access to PropertyValues virtual css::uno::Any getOOXDomValue(svx::diagram::DomMapFlag aDomMapFlag) const = 0; + // check if mandatory DiagramDomS exist and/or were not touched + virtual bool checkMinimalDataDoms() const = 0; + // access to RootShape - the GroupObject used to host this Diagram css::uno::Reference< css::drawing::XShape >& getRootShape() { return accessRootShape(); } }; diff --git a/oox/source/drawingml/diagram/datamodel_oox.cxx b/oox/source/drawingml/diagram/datamodel_oox.cxx index 1a72c837ce18..ec7ce7e4cf17 100644 --- a/oox/source/drawingml/diagram/datamodel_oox.cxx +++ b/oox/source/drawingml/diagram/datamodel_oox.cxx @@ -109,14 +109,15 @@ void DiagramData_oox::writeDiagramReplacement(DrawingML& rOriginalDrawingML, sax rTarget->endDocument(); } -void DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rOriginalFB, sax_fastparser::FSHelperPtr& rTarget) +void DiagramData_oox::writeDiagramData(DrawingML& rOriginalDrawingML, sax_fastparser::FSHelperPtr& rTarget, std::u16string_view rDrawingRelId) { if (!rTarget) return; // write header infos - const OUString aNsDmlDiagram(rOriginalFB.getNamespaceURL(OOX_NS(dmlDiagram))); - const OUString aNsDml(rOriginalFB.getNamespaceURL(OOX_NS(dml))); + ::oox::core::XmlFilterBase* pOriginalFB(rOriginalDrawingML.GetFB()); + const OUString aNsDmlDiagram(pOriginalFB->getNamespaceURL(OOX_NS(dmlDiagram))); + const OUString aNsDml(pOriginalFB->getNamespaceURL(OOX_NS(dml))); rTarget->startElementNS(XML_dgm, XML_dataModel, FSNS(XML_xmlns, XML_dgm), aNsDmlDiagram, FSNS(XML_xmlns, XML_a), aNsDml); @@ -133,19 +134,59 @@ void DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rOriginalFB, sa pAttributeList->add(XML_cxnId, rPoint.msCnxId); rTarget->startElementNS(XML_dgm, XML_pt, pAttributeList); + // write basic Point infos rPoint.writeDiagramData_data(rTarget); - uno::Reference<drawing::XShape> xMasterText(getMasterXShapeForPoint(rPoint)); + // we need to find a XShape related to this Node (rPoint). Not + // all Nodes have an associated XShape. First try direct find, + // that will work e.g. for Objects in the Background (NOT our + // own BGShape) that have no text, but might have a fill + uno::Reference<drawing::XShape> xAssociatedShape(getXShapeByModelID(rPoint.msModelId)); + uno::Reference<beans::XPropertySet> xProps; + bool bWriteFill(false); + bool bWriteText(false); + + if (xAssociatedShape) + { + // only for those mentioned BgShapes because for TextNodes + // (presName="textNode") the fill is written to the associated + // text node (phldrT="[Text]") + if (u"bgShp"_ustr == rPoint.msPresentationLayoutStyleLabel) + { + // check for fill + xProps = uno::Reference<beans::XPropertySet>(xAssociatedShape, uno::UNO_QUERY); + bWriteFill = xProps->getPropertyValue(u"FillStyle"_ustr) != drawing::FillStyle_NONE; + } + } + else + { + // for TextShapes it's more complex: this Node (rPoint) may be the + // Node holdig the text, but the XShape referencing it is associated + // with a Node that references this by using presAssocID. Use + // getMasterXShapeForPoint that uses that association and try + // to access the XShape containing the Text ModelData + xAssociatedShape = getMasterXShapeForPoint(rPoint); + + if (xAssociatedShape) + { + // check for text + uno::Reference<text::XText> xText(xAssociatedShape, uno::UNO_QUERY); + bWriteText= xText && !xText->getString().isEmpty(); + + // check for fill. This is the associated TextNode (phldrT="[Text]") + // and the fill is added here, *not* at the XShape/Model node + xProps = uno::Reference<beans::XPropertySet>(xAssociatedShape, uno::UNO_QUERY); + bWriteFill = xProps->getPropertyValue(u"FillStyle"_ustr) != drawing::FillStyle_NONE; + } + } - if (xMasterText) + if (bWriteText) { rTarget->startElementNS(XML_dgm, XML_t); - DrawingML aTempML(rTarget, &rOriginalFB); - aTempML.WriteText(xMasterText, false, true, XML_a); + DrawingML aTempML(rTarget, pOriginalFB); + aTempML.setDiagaramExport(true); + aTempML.WriteText(xAssociatedShape, false, true, XML_a); rTarget->endElementNS(XML_dgm, XML_t); - - // uno::Reference<beans::XPropertySet> xProps(xBgShape, uno::UNO_QUERY); - // aTempML.WriteFill( xProps, xBgShape->getSize()); } else { @@ -154,7 +195,10 @@ void DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rOriginalFB, sa TypeConstant::XML_sibTrans == rPoint.mnXMLType || "textNode" == rPoint.msPresentationLayoutName); - if (bWriteEmptyText) + // empty text is written by MSO, but may not be needed. For now, just do it + static bool bSuppressEmptyText(false); + + if (bWriteEmptyText && !bSuppressEmptyText) { rTarget->startElementNS(XML_dgm, XML_t); rTarget->singleElementNS(XML_a, XML_bodyPr); @@ -166,8 +210,21 @@ void DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rOriginalFB, sa } } + if (bWriteFill) + { + DrawingML aTempML(rTarget, pOriginalFB); + aTempML.setDiagaramExport(true); + aTempML.WriteFill( xProps, xAssociatedShape->getSize()); + } + else + { + // write empty fill + rTarget->singleElementNS(XML_dgm, XML_spPr); + } + rTarget->endElementNS(XML_dgm, XML_pt); } + rTarget->endElementNS(XML_dgm, XML_ptLst); // write ConnectorList @@ -195,7 +252,8 @@ void DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rOriginalFB, sa { // 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, &rOriginalFB); + DrawingML aTempML(rTarget, pOriginalFB); + aTempML.setDiagaramExport(true); uno::Reference<beans::XPropertySet> xProps(xBgShape, uno::UNO_QUERY); aTempML.WriteFill( xProps, xBgShape->getSize()); } @@ -209,14 +267,14 @@ void DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rOriginalFB, sa // 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(rOriginalFB.getNamespaceURL(OOX_NS(dsp))); + const OUString rNsDsp(pOriginalFB->getNamespaceURL(OOX_NS(dsp))); rTarget->startElementNS(XML_a, XML_ext, XML_uri, rNsDsp); - OUString aDspLine("<dsp:dataModelExt xmlns:dsp=\"" + rNsDsp + "\" "); - if (!getExtDrawings().empty()) + OUString aDspLine(u"<dsp:dataModelExt xmlns:dsp=\""_ustr + rNsDsp + u"\" "_ustr); + if (!rDrawingRelId.empty()) { - aDspLine += "relId=\"" + getExtDrawings().front() + "\" "; + aDspLine += u"relId=\""_ustr + rDrawingRelId + u"\" "_ustr; } - aDspLine += "minVer=\"" + aNsDml + "\"/>"; + aDspLine += u"minVer=\""_ustr + aNsDml + u"\"/>"_ustr; rTarget->write(aDspLine); rTarget->endElementNS(XML_a, XML_ext); rTarget->endElementNS(XML_dgm, XML_extLst); diff --git a/oox/source/drawingml/diagram/datamodel_oox.hxx b/oox/source/drawingml/diagram/datamodel_oox.hxx index b3f6eb85fe8a..75579681d2e0 100644 --- a/oox/source/drawingml/diagram/datamodel_oox.hxx +++ b/oox/source/drawingml/diagram/datamodel_oox.hxx @@ -52,8 +52,9 @@ public: Shape* getOrCreateAssociatedShape(const svx::diagram::Point& rPoint, bool bCreateOnDemand = false) const; + // helpers to write some specific DiagramDoms void writeDiagramReplacement(DrawingML& rOriginalDrawingML, sax_fastparser::FSHelperPtr& rTarget); - void writeDiagramData(oox::core::XmlFilterBase& rOriginalFB, sax_fastparser::FSHelperPtr& rTarget); + void writeDiagramData(DrawingML& rOriginalDrawingML, sax_fastparser::FSHelperPtr& rTarget, std::u16string_view rDrawingRelId); 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 704d0b030ed2..5e4089a9d8e2 100644 --- a/oox/source/drawingml/diagram/diagram.cxx +++ b/oox/source/drawingml/diagram/diagram.cxx @@ -49,6 +49,10 @@ #ifdef DBG_UTIL #include <osl/file.hxx> #include <o3tl/environment.hxx> +#include <tools/stream.hxx> +#include <unotools/streamwrap.hxx> +#include <comphelper/storagehelper.hxx> +#include <com/sun/star/embed/XRelationshipAccess.hpp> #endif using namespace ::com::sun::star; @@ -186,7 +190,10 @@ void Diagram::resetOOXDomValues(svx::diagram::DomMapFlags aDomMapFlags) bool Diagram::checkMinimalDataDoms() const { - if (maDiagramPRDomMap.end() == maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXData)) + // check if re-creation is activated + static bool bReCreateDiagramDataDoms(nullptr != std::getenv("ACTIVATE_RECREATE_DIAGRAM_DATADOMS")); + + if (!bReCreateDiagramDataDoms && maDiagramPRDomMap.end() == maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXData)) return false; if (maDiagramPRDomMap.end() == maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXLayout)) @@ -201,98 +208,83 @@ bool Diagram::checkMinimalDataDoms() const return true; } -void Diagram::tryToCreateMissingDataDoms(DrawingML& rOriginalDrawingML) +void Diagram::writeDiagramOOXData(DrawingML& rOriginalDrawingML, uno::Reference<io::XOutputStream>& xOutputStream, std::u16string_view rDrawingRelId) const { - // internal testing: allow to force to always recreate - 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")); - SAL_INFO("oox", "DiagramReCreate: always==" << bForceAlwaysReCreate << ",bReCreate==" << bReCreateDiagramDataDoms); - if (!bForceAlwaysReCreate && !bReCreateDiagramDataDoms) + if (!xOutputStream) return; - oox::core::XmlFilterBase& rFB(*rOriginalDrawingML.GetFB()); - - if (bForceAlwaysReCreate || maDiagramPRDomMap.end() == maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXData)) - { - // re-create OOXData DomFile from model data - SAL_INFO("oox", "DiagramReCreate: creating DomMapFlag::OOXData"); - 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()->writeDiagramData(rFB, 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(); + // re-create OOXData DomFile from model data + sax_fastparser::FSHelperPtr aFS = std::make_shared<sax_fastparser::FastSerializerHelper>(xOutputStream, true); + getData()->writeDiagramData(rOriginalDrawingML, aFS, rDrawingRelId); - 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::OOXData] <<= xInstance; - } - } + // this call is *important*, without it xDocBuilder->parse below fails and some strange + // and wrong assertion gets thrown in ~FastSerializerHelper that shall get called + xOutputStream->closeOutput(); #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 + "data_T.xml"); - } -#endif + uno::Reference< embed::XRelationshipAccess > xRelations( xOutputStream, uno::UNO_QUERY ); + if( xRelations.is() ) + { + const uno::Sequence<uno::Sequence<beans::StringPair>> aSeqs = xRelations->getAllRelationships(); + for (const uno::Sequence<beans::StringPair>& aSeq : aSeqs) + { + SAL_INFO("oox", "RelationData:"); + for (const beans::StringPair& aPair : aSeq) + SAL_INFO("oox", " Key: " << aPair.First << ", Value: " << aPair.Second); } } - if (bForceAlwaysReCreate || maDiagramPRDomMap.end() == maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXDrawing)) + const OUString env(o3tl::getEnvironment(u"DIAGRAM_DUMP_PATH"_ustr)); + if(!env.isEmpty()) { - // 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(); + OUString url; + ::osl::FileBase::getFileURLFromSystemPath(env, url); + SvFileStream aOutStream(url + "data_T.xml", StreamMode::WRITE|StreamMode::TRUNC); + uno::Reference<io::XStream> xOutStream(new utl::OStreamWrapper(aOutStream)); + uno::Reference<io::XStream> xInStream(xOutputStream, uno::UNO_QUERY); + comphelper::OStorageHelper::CopyInputToOutput(xInStream->getInputStream(), xOutStream->getOutputStream()); + } +#endif +} - if (xOutput) - { - sax_fastparser::FSHelperPtr aFS = std::make_shared<sax_fastparser::FastSerializerHelper>(xOutput, true); - getData()->writeDiagramReplacement(rOriginalDrawingML, aFS); - xOutput->flush(); +void Diagram::writeDiagramOOXDrawing(DrawingML& rOriginalDrawingML, uno::Reference<io::XOutputStream>& xOutputStream) const +{ + if (!xOutputStream) + return; - // 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(); + // re-create OOXDrawing DomFile from model data + SAL_INFO("oox", "DiagramReCreate: creating DomMapFlag::OOXDrawing"); + sax_fastparser::FSHelperPtr aFS = std::make_shared<sax_fastparser::FastSerializerHelper>(xOutputStream, true); + getData()->writeDiagramReplacement(rOriginalDrawingML, aFS); - 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; - } - } + // this call is *important*, without it xDocBuilder->parse below fails and some strange + // and wrong assertion gets thrown in ~FastSerializerHelper that shall get called + xOutputStream->closeOutput(); #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 + uno::Reference< embed::XRelationshipAccess > xRelations( xOutputStream, uno::UNO_QUERY ); + if( xRelations.is() ) + { + const uno::Sequence<uno::Sequence<beans::StringPair>> aSeqs = xRelations->getAllRelationships(); + for (const uno::Sequence<beans::StringPair>& aSeq : aSeqs) + { + SAL_INFO("oox", "RelationDrawing:"); + for (const beans::StringPair& aPair : aSeq) + SAL_INFO("oox", " Key: " << aPair.First << ", Value: " << aPair.Second); } } - // more to do... + const OUString env(o3tl::getEnvironment(u"DIAGRAM_DUMP_PATH"_ustr)); + if(!env.isEmpty()) + { + OUString url; + ::osl::FileBase::getFileURLFromSystemPath(env, url); + SvFileStream aOutStream(url + "drawing_T.xml", StreamMode::WRITE|StreamMode::TRUNC); + uno::Reference<io::XStream> xOutStream(new utl::OStreamWrapper(aOutStream)); + uno::Reference<io::XStream> xInStream(xOutputStream, uno::UNO_QUERY); + comphelper::OStorageHelper::CopyInputToOutput(xInStream->getInputStream(), xOutStream->getOutputStream()); + } +#endif } using ShapePairs diff --git a/oox/source/drawingml/diagram/diagram.hxx b/oox/source/drawingml/diagram/diagram.hxx index 8f79c852932e..4d2008dd9a33 100644 --- a/oox/source/drawingml/diagram/diagram.hxx +++ b/oox/source/drawingml/diagram/diagram.hxx @@ -153,7 +153,10 @@ public: // check if mandatory DiagramDomS exist (or can be created) bool checkMinimalDataDoms() const; - void tryToCreateMissingDataDoms(DrawingML& rOriginalDrawingML); + + // helpers to write some specific DiagramDoms + void writeDiagramOOXData(DrawingML& rOriginalDrawingML, css::uno::Reference<css::io::XOutputStream>& xOutputStream, std::u16string_view rDrawingRelId) const; + void writeDiagramOOXDrawing(DrawingML& rOriginalDrawingML, css::uno::Reference<css::io::XOutputStream>& xOutputStream) const; 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 593a06a96160..c77e2a0a73a5 100644 --- a/oox/source/drawingml/diagram/diagramhelper_oox.cxx +++ b/oox/source/drawingml/diagram/diagramhelper_oox.cxx @@ -48,6 +48,9 @@ DiagramHelper_oox::DiagramHelper_oox(std::shared_ptr<Diagram> xDiagramPtr, : mpDiagramPtr(std::move(xDiagramPtr)) , mpThemePtr(std::move(xTheme)) , maImportSize(aImportSize) + , msNewNodeId() + , msNewNodeText() + , mbInitiallyNoDrawingDom(false) { } @@ -469,12 +472,23 @@ bool DiagramHelper_oox::checkMinimalDataDoms() const return mpDiagramPtr->checkMinimalDataDoms(); } -void DiagramHelper_oox::tryToCreateMissingDataDoms(DrawingML& rOriginalDrawingML) +void DiagramHelper_oox::writeDiagramOOXData(DrawingML& rOriginalDrawingML, + uno::Reference<io::XOutputStream>& xOutputStream, + std::u16string_view rDrawingRelId) const { if (!mpDiagramPtr) return; - mpDiagramPtr->tryToCreateMissingDataDoms(rOriginalDrawingML); + mpDiagramPtr->writeDiagramOOXData(rOriginalDrawingML, xOutputStream, rDrawingRelId); +} + +void DiagramHelper_oox::writeDiagramOOXDrawing( + DrawingML& rOriginalDrawingML, uno::Reference<io::XOutputStream>& xOutputStream) const +{ + if (!mpDiagramPtr) + return; + + mpDiagramPtr->writeDiagramOOXDrawing(rOriginalDrawingML, xOutputStream); } } diff --git a/oox/source/drawingml/graphicshapecontext.cxx b/oox/source/drawingml/graphicshapecontext.cxx index da7b40603cdf..e647d9db7855 100644 --- a/oox/source/drawingml/graphicshapecontext.cxx +++ b/oox/source/drawingml/graphicshapecontext.cxx @@ -40,6 +40,7 @@ #include <oox/ppt/pptshapegroupcontext.hxx> #include <oox/token/namespaces.hxx> #include <oox/token/tokens.hxx> +#include <oox/drawingml/diagram/diagramhelper_oox.hxx> using namespace ::com::sun::star; using namespace ::com::sun::star::io; @@ -313,7 +314,12 @@ ContextHandlerRef DiagramGraphicDataContext::onCreateContext( ::sal_Int32 aEleme // No DrawingML fallback, need to warn the user at the end. if (mpShapePtr->getExtDrawings().empty()) + { getFilter().setMissingExtDrawing(); + DiagramHelper_oox* pHelper(mpShapePtr->getDiagramHelper()); + if (nullptr != pHelper) + pHelper->setInitiallyNoDrawingDom(true); + } else { for (const auto& rRelId : mpShapePtr->getExtDrawings()) diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx index 7a9205fe41d7..99f1893aa172 100644 --- a/oox/source/export/drawingml.cxx +++ b/oox/source/export/drawingml.cxx @@ -1790,7 +1790,10 @@ void DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rX sRelId = writeGraphicToStorage(aGraphic, bRelPathToMedia); - mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId); + if (isDiagaramExport()) + mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)), FSNS(XML_r, XML_embed), sRelId); + else + mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId); const auto& pVectorGraphicDataPtr = aGraphic.getVectorGraphicData(); @@ -2904,11 +2907,18 @@ void DrawingML::WriteRunProperties(const Reference<XPropertySet>& rRun, sal_Int3 sURL, bExtURL); } if (bExtURL) - mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + { + if (isDiagaramExport()) + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)), FSNS(XML_r, XML_id), sRelId); + else + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + } else + { mpFS->singleElementNS( XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, XML_action, sURL.isEmpty() ? "ppaction://noaction" : "ppaction://hlinksldjump"); + } } else { @@ -6790,79 +6800,109 @@ OString DrawingML::WriteWdpPicture( const OUString& rFileId, const Sequence< sal return OUStringToOString(aId, RTL_TEXTENCODING_UTF8); } -bool DrawingML::PrepareToWriteAsDiagram(const css::uno::Reference<css::drawing::XShape>& rXRootShape) -{ - SdrObject* pObj(SdrObject::getSdrObjectFromXShape(rXRootShape)); - - if (nullptr == pObj) - return false; - - const std::shared_ptr<svx::diagram::DiagramHelper_svx>& rIDiagramHelper(pObj->getDiagramHelper()); - - if (!rIDiagramHelper) - return false; - - DiagramHelper_oox* pAdvancedDiagramHelper = static_cast<DiagramHelper_oox*>(rIDiagramHelper.get()); - - if (nullptr == pAdvancedDiagramHelper) - return false; - - // try to re-create (if needed is decided there) - pAdvancedDiagramHelper->tryToCreateMissingDataDoms(*this); - - if (!pAdvancedDiagramHelper->checkMinimalDataDoms()) - return false; - - return true; -} - void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rXShape, sal_Int32 nDiagramId, sal_Int32 nShapeId) { SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape); - assert(pObj && "no SdrObject"); - assert(pObj->isDiagram() && "is no Diagram"); + if (nullptr == pObj) + return; const std::shared_ptr< svx::diagram::DiagramHelper_svx >& rIDiagramHelper(pObj->getDiagramHelper()); - assert(rIDiagramHelper && "has no DiagramHelper"); + if (!rIDiagramHelper) + return; const DiagramHelper_oox* pAdvancedDiagramHelper = static_cast<DiagramHelper_oox*>(rIDiagramHelper.get()); - assert(pAdvancedDiagramHelper && "has no DiagramHelper"); - - if (!pAdvancedDiagramHelper->checkMinimalDataDoms()) + if (nullptr == pAdvancedDiagramHelper) return; - // get/check mandatory DomS - uno::Reference<xml::dom::XDocument> dataDom; - rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXData) >>= dataDom; - assert(dataDom && "mandatory DiagramDom missing"); - + // check mandatory and originally imported DataDom OOXLayout uno::Reference<xml::dom::XDocument> layoutDom; rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXLayout) >>= layoutDom; - assert(layoutDom && "mandatory DiagramDom missing"); + if (!layoutDom) + return; + // check mandatory and originally imported DataDom OOXStyle uno::Reference<xml::dom::XDocument> styleDom; rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXStyle) >>= styleDom; - assert(styleDom && "mandatory DiagramDom missing"); + if (!styleDom) + return; + // check mandatory and originally imported DataDom OOXColor uno::Reference<xml::dom::XDocument> colorDom; rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXColor) >>= colorDom; - assert(colorDom && "mandatory DiagramDom missing"); + if (!colorDom) + return; - // get optional DomS + // handle drawingDom OOXDrawing before OOXData. It may exist if Diagram is untouched + // from original import. it needs a drawingRelId that is referenced inside dataDom uno::Reference<xml::dom::XDocument> drawingDom; rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDrawing) >>= drawingDom; + const OUString drawingFileName("diagrams/drawing" + OUString::number(nDiagramId) + ".xml"); + const OUString sRelationCompPrefix(GetRelationCompPrefix()); + const OUString sDir(GetComponentDir()); + static bool bForceAlwaysReCreate(nullptr != std::getenv("FORCE_RECREATE_DIAGRAM_DATADOMS")); + + // check if re-creation is forced (for test purposes) + if (drawingDom && bForceAlwaysReCreate) + drawingDom.clear(); + + // add relation in any case. If we have a drawingDom the dataDom will be adapted, + // if drawingDom gets written it's needed to embed it there + OUString drawingRelId; + + // CAUTION! We have stuff like testfdo79822 with file "fdo79822.docx" + // that do not originally have a drawingDom at all. The test mentions + // that this file was created in ms word 2007. In those cases we might + // try in the future to 'repair' stuff by writing a DrawingDom, but for + // now just do as version before did - write no DrawingSDom at all + const bool bWriteNoDrawingDomAtAll(pAdvancedDiagramHelper->getInitiallyNoDrawingDom()); + + if (!bWriteNoDrawingDomAtAll) + { + // only add Relation if we write a DrawingDom + drawingRelId = mpFB->addRelation( + mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDRAWING), + Concat2View(sRelationCompPrefix + drawingFileName)); + } - uno::Sequence<uno::Sequence<uno::Any>> xDataImageRelSeq; - rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDataImageRels) - >>= xDataImageRelSeq; + if (!drawingDom && !bWriteNoDrawingDomAtAll) + { + // no drawingDom exists, so it was either not originally imported or the + // Diagram was changed, so it got deleted. write drawingDom directly as + // sub-content + uno::Reference<io::XOutputStream> xOutputStream = mpFB->openFragmentStream( + sDir + "/" + drawingFileName, + u"application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"_ustr); + + if (xOutputStream) + pAdvancedDiagramHelper->writeDiagramOOXDrawing(*this, xOutputStream); + } + + // check DataDom OOXData + uno::Reference<xml::dom::XDocument> dataDom; + rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXData) >>= dataDom; + + // check if re-creation is forced (for test purposes) + if (dataDom && bForceAlwaysReCreate) + dataDom.clear(); - uno::Sequence<uno::Sequence<uno::Any>> xDataHlinkRelSeq; - rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDataHlinkRels) - >>= xDataHlinkRelSeq; + if (!dataDom) + { + // no dataDom exists, so the Diagram was changed, so it got deleted. + // write dataDom directly as sub-content + OUString dataFileName = u"diagrams/data"_ustr + OUString::number(nDiagramId) + u".xml"_ustr; + uno::Reference<io::XOutputStream> xOutputStream = mpFB->openFragmentStream( + sDir + u"/"_ustr + dataFileName, + u"application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"_ustr); + + if (xOutputStream) + pAdvancedDiagramHelper->writeDiagramOOXData(*this, xOutputStream, drawingRelId); + } - // generate a unique id + // prepare AttributeList for export of this GroupObject representing the Diagram + // as reference to Diagram rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + // id in cNvPr under cNvGraphicFramePr must be unique among shapes, // but can be different from diagram's own id const sal_Int32 nExpShapeId = nShapeId != -1 ? nShapeId : nDiagramId; @@ -6919,8 +6959,6 @@ void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rX mpFS->startElementNS(XML_a, XML_graphicData, XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/diagram"); - OUString sRelationCompPrefix = GetRelationCompPrefix(); - // add data relation OUString dataFileName = "diagrams/data" + OUString::number(nDiagramId) + ".xml"; OUString dataRelId = @@ -6945,33 +6983,6 @@ void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rX oox::getRelationship(Relationship::DIAGRAMCOLORS), Concat2View(sRelationCompPrefix + colorFileName)); - OUString drawingFileName; - if (drawingDom.is()) - { - // add drawing relation - drawingFileName = "diagrams/drawing" + OUString::number(nDiagramId) + ".xml"; - OUString drawingRelId = mpFB->addRelation( - mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDRAWING), - Concat2View(sRelationCompPrefix + drawingFileName)); - - // the data dom contains a reference to the drawing relation. We need to update it with the new generated - // relation value before writing the dom to a file - - // Get the dsp:damaModelExt node from the dom - uno::Reference<xml::dom::XNodeList> nodeList = dataDom->getElementsByTagNameNS( - u"http://schemas.microsoft.com/office/drawing/2008/diagram"_ustr, u"dataModelExt"_ustr); - - // There must be one element only so get it - uno::Reference<xml::dom::XNode> node = nodeList->item(0); - - // Get the list of attributes of the node - uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes(); - - // Get the node with the relId attribute and set its new value - uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem(u"relId"_ustr); - relIdNode->setNodeValue(drawingRelId); - } - mpFS->singleElementNS(XML_dgm, XML_relIds, FSNS(XML_xmlns, XML_dgm), mpFB->getNamespaceURL(OOX_NS(dmlDiagram)), FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)), @@ -6985,20 +6996,54 @@ void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rX uno::Reference<xml::sax::XWriter> writer = xml::sax::Writer::create(comphelper::getProcessComponentContext()); - OUString sDir = GetComponentDir(); + if (dataDom) + { + if (!drawingRelId.isEmpty()) + { + // NOTE: drawingRelId may be empty if bWriteNoDrawingDomAtAll is set, see + // comments above + // the original and unchanged dataDom contains a reference to the drawing + // relation. We need to update it with the new generated relation value + // before writing the dom to the file + + // Get the dsp:damaModelExt node from the dom + uno::Reference<xml::dom::XNodeList> nodeList = dataDom->getElementsByTagNameNS( + u"http://schemas.microsoft.com/office/drawing/2008/diagram"_ustr, u"dataModelExt"_ustr); - // write data file - serializer.set(dataDom, uno::UNO_QUERY); - uno::Reference<io::XOutputStream> xDataOutputStream = mpFB->openFragmentStream( - sDir + "/" + dataFileName, - u"application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"_ustr); - writer->setOutputStream(xDataOutputStream); - serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), - uno::Sequence<beans::StringPair>()); + // There must be one element only so get it + uno::Reference<xml::dom::XNode> node = nodeList->item(0); + + // Get the list of attributes of the node + uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes(); + + // Get the node with the relId attribute and set its new value + uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem(u"relId"_ustr); + relIdNode->setNodeValue(drawingRelId); + } + + // write originally imported & untouched data file + serializer.set(dataDom, uno::UNO_QUERY); + uno::Reference<io::XOutputStream> xDataOutputStream = mpFB->openFragmentStream( + sDir + "/" + dataFileName, + u"application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"_ustr); + writer->setOutputStream(xDataOutputStream); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // get originally imported OOXDataImageRels + uno::Sequence<uno::Sequence<uno::Any>> xDataImageRelSeq; + rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDataImageRels) + >>= xDataImageRelSeq; - // write the associated Images and rels for data file - writeDiagramImageRels(xDataImageRelSeq, xDataOutputStream, u"OOXDiagramDataRels", nDiagramId); - writeDiagramHlinkRels(xDataHlinkRelSeq, xDataOutputStream); + // get originally imported OOXDataHlinkRels + uno::Sequence<uno::Sequence<uno::Any>> xDataHlinkRelSeq; + rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDataHlinkRels) + >>= xDataHlinkRelSeq; + + // write the associated Images and rels for data file + writeDiagramImageRels(xDataImageRelSeq, xDataOutputStream, u"OOXDiagramDataRels", nDiagramId); + writeDiagramHlinkRels(xDataHlinkRelSeq, xDataOutputStream); + } // write layout file serializer.set(layoutDom, uno::UNO_QUERY); @@ -7024,23 +7069,23 @@ void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rX serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), uno::Sequence<beans::StringPair>()); - // write drawing file - if (!drawingDom.is()) - return; - - serializer.set(drawingDom, uno::UNO_QUERY); - uno::Reference<io::XOutputStream> xDrawingOutputStream = mpFB->openFragmentStream( - sDir + "/" + drawingFileName, u"application/vnd.ms-office.drawingml.diagramDrawing+xml"_ustr); - writer->setOutputStream(xDrawingOutputStream); - serializer->serialize( - uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), - uno::Sequence<beans::StringPair>()); - - // write the associated Images and rels for drawing file - uno::Sequence<uno::Sequence<uno::Any>> xDrawingRelSeq; - rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDrawingRels) >>= xDrawingRelSeq; - writeDiagramImageRels(xDrawingRelSeq, xDrawingOutputStream, u"OOXDiagramDrawingRels", - nDiagramId); + if (drawingDom.is()) + { + // write original and untouched drawingDom + serializer.set(drawingDom, uno::UNO_QUERY); + uno::Reference<io::XOutputStream> xDrawingOutputStream = mpFB->openFragmentStream( + sDir + "/" + drawingFileName, u"application/vnd.ms-office.drawingml.diagramDrawing+xml"_ustr); + writer->setOutputStream(xDrawingOutputStream); + serializer->serialize( + uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write the associated Images and rels for drawing file + uno::Sequence<uno::Sequence<uno::Any>> xDrawingRelSeq; + rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDrawingRels) >>= xDrawingRelSeq; + writeDiagramImageRels(xDrawingRelSeq, xDrawingOutputStream, u"OOXDiagramDrawingRels", + nDiagramId); + } } void DrawingML::writeDiagramImageRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq, diff --git a/sd/source/filter/eppt/pptx-epptooxml.cxx b/sd/source/filter/eppt/pptx-epptooxml.cxx index 286c2c6e67b9..1ad8035ead0c 100644 --- a/sd/source/filter/eppt/pptx-epptooxml.cxx +++ b/sd/source/filter/eppt/pptx-epptooxml.cxx @@ -2365,13 +2365,21 @@ void PowerPointExport::WriteShapeTree(const FSHelperPtr& pFS, PageType ePageType { SAL_INFO("sd.eppt", "mType: " << mType); const SdrObjGroup* pDiagramCandidate(dynamic_cast<const SdrObjGroup*>(SdrObject::getSdrObjectFromXShape(mXShape))); - bool bIsDiagram(nullptr != pDiagramCandidate && pDiagramCandidate->isDiagram()); + bool bSaveAsDiagram(false); - // check if export as Diagram is possible - if (bIsDiagram && !aDML.PrepareToWriteAsDiagram(mXShape)) - bIsDiagram = false; + if (nullptr != pDiagramCandidate) + { + const std::shared_ptr<svx::diagram::DiagramHelper_svx>& rIDiagramHelper(pDiagramCandidate->getDiagramHelper()); + + if (rIDiagramHelper) + { + // check if all needed data exists to either write unchanged/untouched + // Diagram or with re-creation of some DataDoms + bSaveAsDiagram = rIDiagramHelper->checkMinimalDataDoms(); + } + } - if (bIsDiagram) + if (bSaveAsDiagram) { sal_Int32 nShapeId = aDML.GetNewShapeID(mXShape); SAL_INFO("sd.eppt", "writing Diagram " + OUString::number(mnDiagramId) + " with Shape Id " + OUString::number(nShapeId)); diff --git a/svx/source/diagram/datamodel_svx.cxx b/svx/source/diagram/datamodel_svx.cxx index 6a7bf04612a9..b20188452d4b 100644 --- a/svx/source/diagram/datamodel_svx.cxx +++ b/svx/source/diagram/datamodel_svx.cxx @@ -190,8 +190,6 @@ void Point::writeDiagramData_data(sax_fastparser::FSHelperPtr& rTarget) } else rTarget->singleElementNS(XML_dgm, XML_prSet, pAttributeList); - - rTarget->singleElementNS(XML_dgm, XML_spPr); } DiagramData_svx::DiagramData_svx()
