diff --git a/include/oox/helper/propertymap.hxx b/include/oox/helper/propertymap.hxx index 3e48c9817fd1..423bb11fcdcb 100644 --- a/include/oox/helper/propertymap.hxx +++ b/include/oox/helper/propertymap.hxx @@ -56,6 +56,9 @@ public: /** Returns the name of the passed property identifier. */ static const OUString& getPropertyName( sal_Int32 nPropId ); + /** Returns the property identifier of the passed name. */ + static sal_Int32 getPropertyId( std::u16string_view sPropName ); + /** Returns true, if the map contains a property with the passed identifier. */ bool hasProperty( sal_Int32 nPropId ) const; diff --git a/include/svx/diagram/datamodel.hxx b/include/svx/diagram/datamodel.hxx index d76b5c25b163..a07962497b13 100644 --- a/include/svx/diagram/datamodel.hxx +++ b/include/svx/diagram/datamodel.hxx @@ -28,6 +28,9 @@ #include #include +#include +#include + namespace svx::diagram { enum SVXCORE_DLLPUBLIC TypeConstant { @@ -66,12 +69,32 @@ struct SVXCORE_DLLPUBLIC Connection typedef std::vector< Connection > Connections; +/** Text and properies for a point + * For proof of concept to make TextData available in svx level this + * is in a first run pretty simple, but may need to be extended accodingly + * up to similar data as in oox::drawingml::TextBody. Pls have a look at + * secureDataFromShapeToModelAfterDiagramImport() resp. + * restoreDataFromModelToShapeAfterReCreation() on it's usage/purpose + */ +struct SVXCORE_DLLPUBLIC TextBody +{ + // text from 1st paragraph (1st run) + OUString msText; + + // attributes from TextBody::getTextProperties() + css::uno::Sequence< css::beans::PropertyValue > maTextProps; +}; + +typedef std::shared_ptr< TextBody > TextBodyPtr; + /** A point */ struct SVXCORE_DLLPUBLIC Point { Point(); + TextBodyPtr msTextBody; + OUString msCnxId; OUString msModelId; OUString msColorTransformCategoryId; @@ -143,7 +166,7 @@ public: virtual ~DiagramData(); // creates temporary processing data from model data - virtual void build(bool bClearOoxShapes) = 0; + virtual void buildDiagramDataModel(bool bClearOoxShapes); Connections& getConnections() { return maConnections; } Points& getPoints() { return maPoints; } @@ -156,12 +179,12 @@ public: virtual void dump() const = 0; OUString getString() const; - virtual std::vector> getChildren(const OUString& rParentId) const = 0; - virtual OUString addNode(const OUString& rText) = 0; + std::vector> getChildren(const OUString& rParentId) const; + OUString addNode(const OUString& rText); bool removeNode(const OUString& rNodeId); protected: - virtual void getChildrenString(OUStringBuffer& rBuf, const Point* pPoint, sal_Int32 nLevel) const = 0; + void getChildrenString(OUStringBuffer& rBuf, const Point* pPoint, sal_Int32 nLevel) const; void addConnection(TypeConstant nType, const OUString& sSourceId, const OUString& sDestId); // evtl. existing alternative imported visualization identifier diff --git a/oox/source/drawingml/diagram/datamodel.cxx b/oox/source/drawingml/diagram/datamodel.cxx index 9bd8318c8fe1..30d7e2bb5f40 100644 --- a/oox/source/drawingml/diagram/datamodel.cxx +++ b/oox/source/drawingml/diagram/datamodel.cxx @@ -18,6 +18,7 @@ */ #include "datamodel.hxx" + #include #include #include @@ -25,10 +26,8 @@ #include #include #include -#include #include -#include using namespace ::com::sun::star; @@ -55,16 +54,31 @@ Shape* DiagramData::getOrCreateAssociatedShape(const svx::diagram::Point& rPoint return rShapePtr.get(); } -void DiagramData::restoreDataFromModelToShapeAfterReCreation(const svx::diagram::Point& rPoint, Shape& rNewShape) const +void DiagramData::restoreDataFromModelToShapeAfterReCreation(const svx::diagram::Point& rPoint, Shape& rNewShape) { // If we did create a new oox::drawingml::Shape, directly apply // available data from the Diagram ModelData to it as preparation // This is e.g. the Text, but may get more (styles?) - const auto pTextForShape = maPointTextMap.find(rPoint.msModelId); - if (pTextForShape != maPointTextMap.end()) + if(rPoint.msTextBody && !rPoint.msTextBody->msText.isEmpty()) { - rNewShape.setTextBody(pTextForShape->second); + TextBodyPtr aNewTextBody(std::make_shared()); + rNewShape.setTextBody(aNewTextBody); + TextRunPtr pTextRun = std::make_shared(); + pTextRun->getText() = rPoint.msTextBody->msText; + aNewTextBody->addParagraph().addRun(pTextRun); + + if(rPoint.msTextBody->maTextProps.hasElements()) + { + oox::PropertyMap& rTargetMap(aNewTextBody->getTextProperties().maPropertyMap); + + for (auto const& prop : rPoint.msTextBody->maTextProps) + { + sal_Int32 nPropId(oox::PropertyMap::getPropertyId(prop.Name)); + if(nPropId > 0) + rTargetMap.setAnyProperty(nPropId, prop.Value); + } + } } } @@ -79,18 +93,35 @@ void DiagramData::secureDataFromShapeToModelAfterDiagramImport() { Shape* pShapeCandidate(getOrCreateAssociatedShape(point)); - if(nullptr != pShapeCandidate - && pShapeCandidate->getTextBody() - && !pShapeCandidate->getTextBody()->isEmpty()) + if(nullptr != pShapeCandidate) { - maPointTextMap[point.msModelId] = pShapeCandidate->getTextBody(); + if(pShapeCandidate->getTextBody() && !pShapeCandidate->getTextBody()->isEmpty()) + { + point.msTextBody = std::make_shared(); + point.msTextBody->msText = pShapeCandidate->getTextBody()->toString(); + point.msTextBody->maTextProps = pShapeCandidate->getTextBody()->getTextProperties().maPropertyMap.makePropertyValueSequence(); + } + + // At this place a mechanism to find missing data should be added: + // Create a Shape from so-far secured data & compare it with the + // imported one. Report differences to allow extending the mechanism + // more easily. +#ifdef DBG_UTIL + // The oiginal is pShapeCandidate, re-create potential new oox::drawingml::Shape + // as aNew to be able to compare these + ShapePtr aNew(std::make_shared()); + restoreDataFromModelToShapeAfterReCreation(point, *aNew); + + // Unfortunately oox::drawingml::Shape has no operator==. I tried to add + // one, but that is too expensive. I stopped at oox::drawingml::Color. + // To compare it is necessary to use the debugger, or for single aspects + // of the oox data it might be possible to call local dump() methods at + // both instances to compare them/their output + + // bool bSame(aNew.get() == pShapeCandidate); +#endif } } - - // At this place a mechanism to find missing data should be added: - // Create a Shape from so-far secured data & compare it with the - // imported one. Report differences to allow extending the mechanism - // more easily. } DiagramData::DiagramData() @@ -132,341 +163,29 @@ void DiagramData::dump() const Point_dump(rPoint, getOrCreateAssociatedShape(rPoint)); } -void DiagramData::getChildrenString( - OUStringBuffer& rBuf, - const svx::diagram::Point* pPoint, - sal_Int32 nLevel) const +void DiagramData::buildDiagramDataModel(bool bClearOoxShapes) { - if (!pPoint) - return; - - Shape* pShape(getOrCreateAssociatedShape(*pPoint)); - if(!pShape) - return; - - if (nLevel > 0) - { - for (sal_Int32 i = 0; i < nLevel-1; i++) - rBuf.append('\t'); - rBuf.append('+'); - rBuf.append(' '); - if(pShape->getTextBody()) - rBuf.append(pShape->getTextBody()->toString()); - rBuf.append('\n'); - } - - std::vector< const svx::diagram::Point* > aChildren; - for (const auto& rCxn : maConnections) - if (rCxn.mnXMLType == svx::diagram::TypeConstant::XML_parOf && rCxn.msSourceId == pPoint->msModelId) - { - if (rCxn.mnSourceOrder >= static_cast(aChildren.size())) - aChildren.resize(rCxn.mnSourceOrder + 1); - const auto pChild = maPointNameMap.find(rCxn.msDestId); - if (pChild != maPointNameMap.end()) - aChildren[rCxn.mnSourceOrder] = pChild->second; - } - - for (auto pChild : aChildren) - getChildrenString(rBuf, pChild, nLevel + 1); -} - -std::vector> DiagramData::getChildren(const OUString& rParentId) const -{ - const OUString sModelId = rParentId.isEmpty() ? getRootPoint()->msModelId : rParentId; - std::vector> aChildren; - for (const auto& rCxn : maConnections) - if (rCxn.mnXMLType == svx::diagram::TypeConstant::XML_parOf && rCxn.msSourceId == sModelId) - { - if (rCxn.mnSourceOrder >= static_cast(aChildren.size())) - aChildren.resize(rCxn.mnSourceOrder + 1); - const auto pChild = maPointNameMap.find(rCxn.msDestId); - if (pChild != maPointNameMap.end()) - { - Shape* pShape(getOrCreateAssociatedShape(*(pChild->second))); - aChildren[rCxn.mnSourceOrder] = std::make_pair( - pChild->second->msModelId, - nullptr != pShape && pShape->getTextBody() ? pShape->getTextBody()->toString() : OUString()); - } - } - - // HACK: empty items shouldn't appear there - aChildren.erase(std::remove_if(aChildren.begin(), aChildren.end(), - [](const std::pair& aItem) { return aItem.first.isEmpty(); }), - aChildren.end()); - - return aChildren; -} - -OUString DiagramData::addNode(const OUString& rText) -{ - const svx::diagram::Point& rDataRoot = *getRootPoint(); - OUString sPresRoot; - for (const auto& aCxn : maConnections) - if (aCxn.mnXMLType == svx::diagram::TypeConstant::XML_presOf && aCxn.msSourceId == rDataRoot.msModelId) - sPresRoot = aCxn.msDestId; - - if (sPresRoot.isEmpty()) - return OUString(); - - OUString sNewNodeId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8); - - svx::diagram::Point aDataPoint; - aDataPoint.mnXMLType = svx::diagram::TypeConstant::XML_node; - aDataPoint.msModelId = sNewNodeId; - - Shape* pShape(getOrCreateAssociatedShape(aDataPoint, true)); - pShape->setTextBody(std::make_shared()); - TextRunPtr pTextRun = std::make_shared(); - pTextRun->getText() = rText; - pShape->getTextBody()->addParagraph().addRun(pTextRun); - - OUString sDataSibling; - for (const auto& aCxn : maConnections) - if (aCxn.mnXMLType == svx::diagram::TypeConstant::XML_parOf && aCxn.msSourceId == rDataRoot.msModelId) - sDataSibling = aCxn.msDestId; - - OUString sPresSibling; - for (const auto& aCxn : maConnections) - if (aCxn.mnXMLType == svx::diagram::TypeConstant::XML_presOf && aCxn.msSourceId == sDataSibling) - sPresSibling = aCxn.msDestId; - - svx::diagram::Point aPresPoint; - aPresPoint.mnXMLType = svx::diagram::TypeConstant::XML_pres; - aPresPoint.msModelId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8); - - // create pesPoint shape - getOrCreateAssociatedShape(aPresPoint, true); - - aPresPoint.msPresentationAssociationId = aDataPoint.msModelId; - if (!sPresSibling.isEmpty()) - { - // no idea where to get these values from, so copy from previous sibling - const svx::diagram::Point* pSiblingPoint = maPointNameMap[sPresSibling]; - aPresPoint.msPresentationLayoutName = pSiblingPoint->msPresentationLayoutName; - aPresPoint.msPresentationLayoutStyleLabel = pSiblingPoint->msPresentationLayoutStyleLabel; - aPresPoint.mnLayoutStyleIndex = pSiblingPoint->mnLayoutStyleIndex; - aPresPoint.mnLayoutStyleCount = pSiblingPoint->mnLayoutStyleCount; - } - - addConnection(svx::diagram::TypeConstant::XML_parOf, rDataRoot.msModelId, aDataPoint.msModelId); - addConnection(svx::diagram::TypeConstant::XML_presParOf, sPresRoot, aPresPoint.msModelId); - addConnection(svx::diagram::TypeConstant::XML_presOf, aDataPoint.msModelId, aPresPoint.msModelId); - - // adding at the end, so that references are not invalidated in between - maPoints.push_back(aDataPoint); - maPoints.push_back(aPresPoint); - - build(true); - return sNewNodeId; -} - -#ifdef DEBUG_OOX_DIAGRAM -OString normalizeDotName( const OUString& rStr ) -{ - OUStringBuffer aBuf; - aBuf.append('N'); - - const sal_Int32 nLen(rStr.getLength()); - sal_Int32 nCurrIndex(0); - while( nCurrIndex < nLen ) - { - const sal_Int32 aChar=rStr.iterateCodePoints(&nCurrIndex); - if( aChar != '-' && aChar != '{' && aChar != '}' ) - aBuf.append((sal_Unicode)aChar); - } - - return OUStringToOString(aBuf.makeStringAndClear(), - RTL_TEXTENCODING_UTF8); -} -#endif - -static sal_Int32 calcDepth( std::u16string_view rNodeName, - const svx::diagram::Connections& rCnx ) -{ - // find length of longest path in 'isChild' graph, ending with rNodeName - for (auto const& elem : rCnx) - { - if( !elem.msParTransId.isEmpty() && - !elem.msSibTransId.isEmpty() && - !elem.msSourceId.isEmpty() && - !elem.msDestId.isEmpty() && - elem.mnXMLType == svx::diagram::TypeConstant::XML_parOf && - rNodeName == elem.msDestId ) - { - return calcDepth(elem.msSourceId, rCnx) + 1; - } - } - - return 0; -} - -void DiagramData::build(bool bClearOoxShapes) -{ - // Delete/remove all existing oox::drawingml::Shape if(bClearOoxShapes) { + // Delete/remove all existing oox::drawingml::Shape maPointShapeMap.clear(); } - // build name-object maps - maPointNameMap.clear(); - maPointsPresNameMap.clear(); - maConnectionNameMap.clear(); - maPresOfNameMap.clear(); + // call parent + svx::diagram::DiagramData::buildDiagramDataModel(bClearOoxShapes); -#ifdef DEBUG_OOX_DIAGRAM - std::ofstream output("tree.dot"); - - output << "digraph datatree {" << std::endl; -#endif - svx::diagram::Points& rPoints = getPoints(); - for (auto & point : rPoints) + if(bClearOoxShapes) { -#ifdef DEBUG_OOX_DIAGRAM - output << "\t" - << normalizeDotName(point.msModelId).getStr() - << "["; + // re-create all existing oox::drawingml::Shape + svx::diagram::Points& rPoints = getPoints(); - if( !point.msPresentationLayoutName.isEmpty() ) - output << "label=\"" - << OUStringToOString( - point.msPresentationLayoutName, - RTL_TEXTENCODING_UTF8).getStr() << "\", "; - else - output << "label=\"" - << OUStringToOString( - point.msModelId, - RTL_TEXTENCODING_UTF8).getStr() << "\", "; - - switch( point.mnType ) + for (auto & point : rPoints) { - case XML_doc: output << "style=filled, color=red"; break; - case XML_asst: output << "style=filled, color=green"; break; - default: - case XML_node: output << "style=filled, color=blue"; break; - case XML_pres: output << "style=filled, color=yellow"; break; - case XML_parTrans: output << "color=grey"; break; - case XML_sibTrans: output << " "; break; - } - - output << "];" << std::endl; -#endif - - // Create/get shape. Re-create here, that may also set needed - // and available data from the Diagram ModelData at the Shape - Shape* pShape(getOrCreateAssociatedShape(point, true)); - - // does currpoint have any text set? - if( nullptr != pShape && pShape->getTextBody() && !pShape->getTextBody()->isEmpty() ) - { -#ifdef DEBUG_OOX_DIAGRAM - static sal_Int32 nCount=0; - output << "\t" - << "textNode" << nCount - << " [" - << "label=\"" - << OUStringToOString( - pShape->getTextBody()->toString(), - RTL_TEXTENCODING_UTF8).getStr() - << "\"" << "];" << std::endl; - output << "\t" - << normalizeDotName(point.msModelId).getStr() - << " -> " - << "textNode" << nCount++ - << ";" << std::endl; -#endif - } - - const bool bInserted1 = getPointNameMap().insert( - std::make_pair(point.msModelId,&point)).second; - - SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique point model id"); - - if( !point.msPresentationLayoutName.isEmpty() ) - { - DiagramData::PointsNameMap::value_type::second_type& rVec= - getPointsPresNameMap()[point.msPresentationLayoutName]; - rVec.push_back(&point); + // Create/get shape. Re-create here, that may also set needed + // and available data from the Diagram ModelData at the Shape + getOrCreateAssociatedShape(point, true); } } - - const svx::diagram::Connections& rConnections = getConnections(); - for (auto const& connection : rConnections) - { -#ifdef DEBUG_OOX_DIAGRAM - if( !connection.msParTransId.isEmpty() || - !connection.msSibTransId.isEmpty() ) - { - if( !connection.msSourceId.isEmpty() || - !connection.msDestId.isEmpty() ) - { - output << "\t" - << normalizeDotName(connection.msSourceId).getStr() - << " -> " - << normalizeDotName(connection.msParTransId).getStr() - << " -> " - << normalizeDotName(connection.msSibTransId).getStr() - << " -> " - << normalizeDotName(connection.msDestId).getStr() - << " [style=dotted," - << ((connection.mnType == XML_presOf) ? " color=red, " : ((connection.mnType == XML_presParOf) ? " color=green, " : " ")) - << "label=\"" - << OUStringToOString(connection.msModelId, - RTL_TEXTENCODING_UTF8 ).getStr() - << "\"];" << std::endl; - } - else - { - output << "\t" - << normalizeDotName(connection.msParTransId).getStr() - << " -> " - << normalizeDotName(connection.msSibTransId).getStr() - << " [" - << ((connection.mnType == XML_presOf) ? " color=red, " : ((connection.mnType == XML_presParOf) ? " color=green, " : " ")) - << "label=\"" - << OUStringToOString(connection.msModelId, - RTL_TEXTENCODING_UTF8 ).getStr() - << "\"];" << std::endl; - } - } - else if( !connection.msSourceId.isEmpty() || - !connection.msDestId.isEmpty() ) - output << "\t" - << normalizeDotName(connection.msSourceId).getStr() - << " -> " - << normalizeDotName(connection.msDestId).getStr() - << " [label=\"" - << OUStringToOString(connection.msModelId, - RTL_TEXTENCODING_UTF8 ).getStr() - << ((connection.mnType == XML_presOf) ? "\", color=red]" : ((connection.mnType == XML_presParOf) ? "\", color=green]" : "\"]")) - << ";" << std::endl; -#endif - - const bool bInserted1 = maConnectionNameMap.insert( - std::make_pair(connection.msModelId,&connection)).second; - - SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique connection model id"); - - if( connection.mnXMLType == svx::diagram::TypeConstant::XML_presOf ) - { - DiagramData::StringMap::value_type::second_type& rVec = getPresOfNameMap()[connection.msDestId]; - rVec[connection.mnDestOrder] = { connection.msSourceId, sal_Int32(0) }; - } - } - - // assign outline levels - DiagramData::StringMap& rStringMap = getPresOfNameMap(); - for (auto & elemPresOf : rStringMap) - { - for (auto & elem : elemPresOf.second) - { - const sal_Int32 nDepth = calcDepth(elem.second.msSourceId, getConnections()); - elem.second.mnDepth = nDepth != 0 ? nDepth : -1; - } - } -#ifdef DEBUG_OOX_DIAGRAM - output << "}" << std::endl; -#endif } } diff --git a/oox/source/drawingml/diagram/datamodel.hxx b/oox/source/drawingml/diagram/datamodel.hxx index 3f410e5d9033..95383ad1da77 100644 --- a/oox/source/drawingml/diagram/datamodel.hxx +++ b/oox/source/drawingml/diagram/datamodel.hxx @@ -37,35 +37,27 @@ class DiagramData : public svx::diagram::DiagramData { public: typedef std::map< OUString, ShapePtr > PointShapeMap; - typedef std::map< OUString, TextBodyPtr > PointTextMap; DiagramData(); virtual ~DiagramData(); // creates temporary processing data from model data - virtual void build(bool bClearOoxShapes); + virtual void buildDiagramDataModel(bool bClearOoxShapes); FillPropertiesPtr& getFillProperties() { return mpFillProperties; } virtual void dump() const; - virtual std::vector> getChildren(const OUString& rParentId) const; - virtual OUString addNode(const OUString& rText); - Shape* getOrCreateAssociatedShape(const svx::diagram::Point& rPoint, bool bCreateOnDemand = false) const; // get/set data between Diagram DataModel and oox::drawingml::Shape void secureDataFromShapeToModelAfterDiagramImport(); - void restoreDataFromModelToShapeAfterReCreation(const svx::diagram::Point& rPoint, Shape& rNewShape) const; + static void restoreDataFromModelToShapeAfterReCreation(const svx::diagram::Point& rPoint, Shape& rNewShape); protected: - virtual void getChildrenString(OUStringBuffer& rBuf, const svx::diagram::Point* pPoint, sal_Int32 nLevel) const; - // The model definition, the parts *only* available in oox. Also look for already // defined ModelData in svx::diagram::DiagramData // - FillStyle - // - Texts for oox::drawingml::Points/svx::diagram::Points, associated by ModelId FillPropertiesPtr mpFillProperties; - PointTextMap maPointTextMap; // temporary processing data, deleted when using build() PointShapeMap maPointShapeMap; diff --git a/oox/source/drawingml/diagram/diagram.cxx b/oox/source/drawingml/diagram/diagram.cxx index 070b365eea14..6684ef913995 100644 --- a/oox/source/drawingml/diagram/diagram.cxx +++ b/oox/source/drawingml/diagram/diagram.cxx @@ -410,7 +410,7 @@ void loadDiagram( ShapePtr const & pShape, // collect data, init maps // for Diagram import, do - for now - NOT clear all oox::drawingml::Shape - pData->build(false); + pData->buildDiagramDataModel(false); // diagram loaded. now lump together & attach to shape pDiagram->addTo(pShape); diff --git a/oox/source/helper/propertymap.cxx b/oox/source/helper/propertymap.cxx index 83495dc879b1..608614441b21 100644 --- a/oox/source/helper/propertymap.cxx +++ b/oox/source/helper/propertymap.cxx @@ -224,6 +224,22 @@ const OUString& PropertyMap::getPropertyName( sal_Int32 nPropId ) return GetPropertyNameVector()[ nPropId ]; } +sal_Int32 PropertyMap::getPropertyId( std::u16string_view sPropName ) +{ + // This may use a std::map to get faster from String to ID in the + // future, inside the [0..PROP_COUNT[ entries. Since it is currently + // only used for Diagram re-creation I opted fo less mem usage here + if(sPropName.empty()) + return -1; + + const std::vector& rVec(GetPropertyNameVector()); + for(size_t a(0); a < rVec.size(); a++) + if(rVec[a] == sPropName) + return a; + + return -1; +} + void PropertyMap::assignAll( const PropertyMap& rPropMap ) { for (auto const& prop : rPropMap.maProperties) diff --git a/svx/source/diagram/datamodel.cxx b/svx/source/diagram/datamodel.cxx index 11b71240b6ec..a586f0556d31 100644 --- a/svx/source/diagram/datamodel.cxx +++ b/svx/source/diagram/datamodel.cxx @@ -19,8 +19,10 @@ #include #include +#include #include +#include #include namespace svx::diagram { @@ -135,10 +137,128 @@ bool DiagramData::removeNode(const OUString& rNodeId) // TODO: fix source/dest order - build(true); + buildDiagramDataModel(true); + return true; } +void DiagramData::getChildrenString( + OUStringBuffer& rBuf, + const svx::diagram::Point* pPoint, + sal_Int32 nLevel) const +{ + if (!pPoint) + return; + + if (nLevel > 0) + { + for (sal_Int32 i = 0; i < nLevel-1; i++) + rBuf.append('\t'); + rBuf.append('+'); + rBuf.append(' '); + if(pPoint->msTextBody) + rBuf.append(pPoint->msTextBody->msText); + rBuf.append('\n'); + } + + std::vector< const svx::diagram::Point* > aChildren; + for (const auto& rCxn : maConnections) + if (rCxn.mnXMLType == TypeConstant::XML_parOf && rCxn.msSourceId == pPoint->msModelId) + { + if (rCxn.mnSourceOrder >= static_cast(aChildren.size())) + aChildren.resize(rCxn.mnSourceOrder + 1); + const auto pChild = maPointNameMap.find(rCxn.msDestId); + if (pChild != maPointNameMap.end()) + aChildren[rCxn.mnSourceOrder] = pChild->second; + } + + for (auto pChild : aChildren) + getChildrenString(rBuf, pChild, nLevel + 1); +} + +std::vector> DiagramData::getChildren(const OUString& rParentId) const +{ + const OUString sModelId = rParentId.isEmpty() ? getRootPoint()->msModelId : rParentId; + std::vector> aChildren; + for (const auto& rCxn : maConnections) + if (rCxn.mnXMLType == TypeConstant::XML_parOf && rCxn.msSourceId == sModelId) + { + if (rCxn.mnSourceOrder >= static_cast(aChildren.size())) + aChildren.resize(rCxn.mnSourceOrder + 1); + const auto pChild = maPointNameMap.find(rCxn.msDestId); + if (pChild != maPointNameMap.end()) + { + aChildren[rCxn.mnSourceOrder] = std::make_pair( + pChild->second->msModelId, + pChild->second->msTextBody ? pChild->second->msTextBody->msText : OUString()); + } + } + + // HACK: empty items shouldn't appear there + aChildren.erase(std::remove_if(aChildren.begin(), aChildren.end(), + [](const std::pair& aItem) { return aItem.first.isEmpty(); }), + aChildren.end()); + + return aChildren; +} + +OUString DiagramData::addNode(const OUString& rText) +{ + const svx::diagram::Point& rDataRoot = *getRootPoint(); + OUString sPresRoot; + for (const auto& aCxn : maConnections) + if (aCxn.mnXMLType == TypeConstant::XML_presOf && aCxn.msSourceId == rDataRoot.msModelId) + sPresRoot = aCxn.msDestId; + + if (sPresRoot.isEmpty()) + return OUString(); + + OUString sNewNodeId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8); + + svx::diagram::Point aDataPoint; + aDataPoint.mnXMLType = TypeConstant::XML_node; + aDataPoint.msModelId = sNewNodeId; + aDataPoint.msTextBody = std::make_shared(); + aDataPoint.msTextBody->msText = rText; + + OUString sDataSibling; + for (const auto& aCxn : maConnections) + if (aCxn.mnXMLType == TypeConstant::XML_parOf && aCxn.msSourceId == rDataRoot.msModelId) + sDataSibling = aCxn.msDestId; + + OUString sPresSibling; + for (const auto& aCxn : maConnections) + if (aCxn.mnXMLType == TypeConstant::XML_presOf && aCxn.msSourceId == sDataSibling) + sPresSibling = aCxn.msDestId; + + svx::diagram::Point aPresPoint; + aPresPoint.mnXMLType = TypeConstant::XML_pres; + aPresPoint.msModelId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8); + + aPresPoint.msPresentationAssociationId = aDataPoint.msModelId; + if (!sPresSibling.isEmpty()) + { + // no idea where to get these values from, so copy from previous sibling + const svx::diagram::Point* pSiblingPoint = maPointNameMap[sPresSibling]; + aPresPoint.msPresentationLayoutName = pSiblingPoint->msPresentationLayoutName; + aPresPoint.msPresentationLayoutStyleLabel = pSiblingPoint->msPresentationLayoutStyleLabel; + aPresPoint.mnLayoutStyleIndex = pSiblingPoint->mnLayoutStyleIndex; + aPresPoint.mnLayoutStyleCount = pSiblingPoint->mnLayoutStyleCount; + } + + addConnection(svx::diagram::TypeConstant::XML_parOf, rDataRoot.msModelId, aDataPoint.msModelId); + addConnection(svx::diagram::TypeConstant::XML_presParOf, sPresRoot, aPresPoint.msModelId); + addConnection(svx::diagram::TypeConstant::XML_presOf, aDataPoint.msModelId, aPresPoint.msModelId); + + // adding at the end, so that references are not invalidated in between + maPoints.push_back(aDataPoint); + maPoints.push_back(aPresPoint); + + buildDiagramDataModel(true); + + return sNewNodeId; +} + void DiagramData::addConnection(svx::diagram::TypeConstant nType, const OUString& sSourceId, const OUString& sDestId) { sal_Int32 nMaxOrd = -1; @@ -153,6 +273,206 @@ void DiagramData::addConnection(svx::diagram::TypeConstant nType, const OUString rCxn.mnSourceOrder = nMaxOrd + 1; } +// #define DEBUG_OOX_DIAGRAM +#ifdef DEBUG_OOX_DIAGRAM +OString normalizeDotName( const OUString& rStr ) +{ + OUStringBuffer aBuf; + aBuf.append('N'); + + const sal_Int32 nLen(rStr.getLength()); + sal_Int32 nCurrIndex(0); + while( nCurrIndex < nLen ) + { + const sal_Int32 aChar=rStr.iterateCodePoints(&nCurrIndex); + if( aChar != '-' && aChar != '{' && aChar != '}' ) + aBuf.append((sal_Unicode)aChar); + } + + return OUStringToOString(aBuf.makeStringAndClear(), + RTL_TEXTENCODING_UTF8); +} +#endif + +static sal_Int32 calcDepth( std::u16string_view rNodeName, + const svx::diagram::Connections& rCnx ) +{ + // find length of longest path in 'isChild' graph, ending with rNodeName + for (auto const& elem : rCnx) + { + if( !elem.msParTransId.isEmpty() && + !elem.msSibTransId.isEmpty() && + !elem.msSourceId.isEmpty() && + !elem.msDestId.isEmpty() && + elem.mnXMLType == TypeConstant::XML_parOf && + rNodeName == elem.msDestId ) + { + return calcDepth(elem.msSourceId, rCnx) + 1; + } + } + + return 0; +} + +void DiagramData::buildDiagramDataModel(bool /*bClearOoxShapes*/) +{ + // build name-object maps + maPointNameMap.clear(); + maPointsPresNameMap.clear(); + maConnectionNameMap.clear(); + maPresOfNameMap.clear(); + +#ifdef DEBUG_OOX_DIAGRAM + std::ofstream output("tree.dot"); + + output << "digraph datatree {" << std::endl; +#endif + svx::diagram::Points& rPoints = getPoints(); + for (auto & point : rPoints) + { +#ifdef DEBUG_OOX_DIAGRAM + output << "\t" + << normalizeDotName(point.msModelId).getStr() + << "["; + + if( !point.msPresentationLayoutName.isEmpty() ) + output << "label=\"" + << OUStringToOString( + point.msPresentationLayoutName, + RTL_TEXTENCODING_UTF8).getStr() << "\", "; + else + output << "label=\"" + << OUStringToOString( + point.msModelId, + RTL_TEXTENCODING_UTF8).getStr() << "\", "; + + switch( point.mnXMLType ) + { + case TypeConstant::XML_doc: output << "style=filled, color=red"; break; + case TypeConstant::XML_asst: output << "style=filled, color=green"; break; + default: + case TypeConstant::XML_node: output << "style=filled, color=blue"; break; + case TypeConstant::XML_pres: output << "style=filled, color=yellow"; break; + case TypeConstant::XML_parTrans: output << "color=grey"; break; + case TypeConstant::XML_sibTrans: output << " "; break; + } + + output << "];" << std::endl; +#endif + + // does currpoint have any text set? + if(point.msTextBody && !point.msTextBody->msText.isEmpty()) + { +#ifdef DEBUG_OOX_DIAGRAM + static sal_Int32 nCount=0; + output << "\t" + << "textNode" << nCount + << " [" + << "label=\"" + << OUStringToOString( + point.msTextBody->msText, + RTL_TEXTENCODING_UTF8).getStr() + << "\"" << "];" << std::endl; + output << "\t" + << normalizeDotName(point.msModelId).getStr() + << " -> " + << "textNode" << nCount++ + << ";" << std::endl; +#endif + } + + const bool bInserted1 = getPointNameMap().insert( + std::make_pair(point.msModelId,&point)).second; + + SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique point model id"); + + if( !point.msPresentationLayoutName.isEmpty() ) + { + DiagramData::PointsNameMap::value_type::second_type& rVec= + getPointsPresNameMap()[point.msPresentationLayoutName]; + rVec.push_back(&point); + } + } + + const svx::diagram::Connections& rConnections = getConnections(); + for (auto const& connection : rConnections) + { +#ifdef DEBUG_OOX_DIAGRAM + if( !connection.msParTransId.isEmpty() || + !connection.msSibTransId.isEmpty() ) + { + if( !connection.msSourceId.isEmpty() || + !connection.msDestId.isEmpty() ) + { + output << "\t" + << normalizeDotName(connection.msSourceId).getStr() + << " -> " + << normalizeDotName(connection.msParTransId).getStr() + << " -> " + << normalizeDotName(connection.msSibTransId).getStr() + << " -> " + << normalizeDotName(connection.msDestId).getStr() + << " [style=dotted," + << ((connection.mnXMLType == TypeConstant::XML_presOf) ? " color=red, " : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? " color=green, " : " ")) + << "label=\"" + << OUStringToOString(connection.msModelId, + RTL_TEXTENCODING_UTF8 ).getStr() + << "\"];" << std::endl; + } + else + { + output << "\t" + << normalizeDotName(connection.msParTransId).getStr() + << " -> " + << normalizeDotName(connection.msSibTransId).getStr() + << " [" + << ((connection.mnXMLType == TypeConstant::XML_presOf) ? " color=red, " : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? " color=green, " : " ")) + << "label=\"" + << OUStringToOString(connection.msModelId, + RTL_TEXTENCODING_UTF8 ).getStr() + << "\"];" << std::endl; + } + } + else if( !connection.msSourceId.isEmpty() || + !connection.msDestId.isEmpty() ) + output << "\t" + << normalizeDotName(connection.msSourceId).getStr() + << " -> " + << normalizeDotName(connection.msDestId).getStr() + << " [label=\"" + << OUStringToOString(connection.msModelId, + RTL_TEXTENCODING_UTF8 ).getStr() + << ((connection.mnXMLType == TypeConstant::XML_presOf) ? "\", color=red]" : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? "\", color=green]" : "\"]")) + << ";" << std::endl; +#endif + + const bool bInserted1 = maConnectionNameMap.insert( + std::make_pair(connection.msModelId,&connection)).second; + + SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique connection model id"); + + if( connection.mnXMLType == TypeConstant::XML_presOf ) + { + DiagramData::StringMap::value_type::second_type& rVec = getPresOfNameMap()[connection.msDestId]; + rVec[connection.mnDestOrder] = { connection.msSourceId, sal_Int32(0) }; + } + } + + // assign outline levels + DiagramData::StringMap& rStringMap = getPresOfNameMap(); + for (auto & elemPresOf : rStringMap) + { + for (auto & elem : elemPresOf.second) + { + const sal_Int32 nDepth = calcDepth(elem.second.msSourceId, getConnections()); + elem.second.mnDepth = nDepth != 0 ? nDepth : -1; + } + } +#ifdef DEBUG_OOX_DIAGRAM + output << "}" << std::endl; +#endif +} + } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */