diff options
Diffstat (limited to 'oox/source/drawingml/diagram/datamodel.cxx')
-rw-r--r-- | oox/source/drawingml/diagram/datamodel.cxx | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/oox/source/drawingml/diagram/datamodel.cxx b/oox/source/drawingml/diagram/datamodel.cxx new file mode 100644 index 000000000..469f40436 --- /dev/null +++ b/oox/source/drawingml/diagram/datamodel.cxx @@ -0,0 +1,475 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "datamodel.hxx" +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <drawingml/fillproperties.hxx> +#include <drawingml/textbody.hxx> +#include <drawingml/textparagraph.hxx> +#include <drawingml/textrun.hxx> +#include <oox/drawingml/shape.hxx> +#include <comphelper/xmltools.hxx> + +#include <unordered_set> +#include <fstream> + +using namespace ::com::sun::star; + +namespace oox::drawingml { + +namespace dgm { + +void Connection::dump() const +{ + SAL_INFO( + "oox.drawingml", + "cnx modelId " << msModelId << ", srcId " << msSourceId << ", dstId " + << msDestId << ", parTransId " << msParTransId << ", presId " + << msPresId << ", sibTransId " << msSibTransId << ", srcOrd " + << mnSourceOrder << ", dstOrd " << mnDestOrder); +} + +void Point::dump() const +{ + SAL_INFO( + "oox.drawingml", + "pt text " << mpShape.get() << ", cnxId " << msCnxId << ", modelId " + << msModelId << ", type " << mnType); +} + +} // oox::drawingml::dgm namespace + +DiagramData::DiagramData() : + mpFillProperties( std::make_shared<FillProperties>() ) +{ +} + +const dgm::Point* DiagramData::getRootPoint() const +{ + for (const auto & aCurrPoint : maPoints) + if (aCurrPoint.mnType == XML_doc) + return &aCurrPoint; + + SAL_WARN("oox.drawingml", "No root point"); + return nullptr; +} + +void DiagramData::dump() const +{ + SAL_INFO("oox.drawingml", "Dgm: DiagramData # of cnx: " << maConnections.size() ); + for (const auto& rConnection : maConnections) + rConnection.dump(); + + SAL_INFO("oox.drawingml", "Dgm: DiagramData # of pt: " << maPoints.size() ); + for (const auto& rPoint : maPoints) + rPoint.dump(); +} + +void DiagramData::getChildrenString(OUStringBuffer& rBuf, const dgm::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(' '); + rBuf.append(pPoint->mpShape->getTextBody()->toString()); + rBuf.append('\n'); + } + + std::vector<const dgm::Point*> aChildren; + for (const auto& rCxn : maConnections) + if (rCxn.mnType == XML_parOf && rCxn.msSourceId == pPoint->msModelId) + { + if (rCxn.mnSourceOrder >= static_cast<sal_Int32>(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); +} + +OUString DiagramData::getString() const +{ + OUStringBuffer aBuf; + const dgm::Point* pPoint = getRootPoint(); + getChildrenString(aBuf, pPoint, 0); + return aBuf.makeStringAndClear(); +} + +std::vector<std::pair<OUString, OUString>> DiagramData::getChildren(const OUString& rParentId) const +{ + const OUString sModelId = rParentId.isEmpty() ? getRootPoint()->msModelId : rParentId; + std::vector<std::pair<OUString, OUString>> aChildren; + for (const auto& rCxn : maConnections) + if (rCxn.mnType == XML_parOf && rCxn.msSourceId == sModelId) + { + if (rCxn.mnSourceOrder >= static_cast<sal_Int32>(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->mpShape->getTextBody()->toString()); + } + + // HACK: empty items shouldn't appear there + aChildren.erase(std::remove_if(aChildren.begin(), aChildren.end(), + [](const std::pair<OUString, OUString>& aItem) { return aItem.first.isEmpty(); }), + aChildren.end()); + + return aChildren; +} + +void DiagramData::addConnection(sal_Int32 nType, const OUString& sSourceId, const OUString& sDestId) +{ + sal_Int32 nMaxOrd = -1; + for (const auto& aCxn : maConnections) + if (aCxn.mnType == nType && aCxn.msSourceId == sSourceId) + nMaxOrd = std::max(nMaxOrd, aCxn.mnSourceOrder); + + dgm::Connection& rCxn = maConnections.emplace_back(); + rCxn.mnType = nType; + rCxn.msSourceId = sSourceId; + rCxn.msDestId = sDestId; + rCxn.mnSourceOrder = nMaxOrd + 1; +} + +OUString DiagramData::addNode(const OUString& rText) +{ + const dgm::Point& rDataRoot = *getRootPoint(); + OUString sPresRoot; + for (const auto& aCxn : maConnections) + if (aCxn.mnType == XML_presOf && aCxn.msSourceId == rDataRoot.msModelId) + sPresRoot = aCxn.msDestId; + + if (sPresRoot.isEmpty()) + return OUString(); + + OUString sNewNodeId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8); + + dgm::Point aDataPoint; + aDataPoint.mnType = XML_node; + aDataPoint.msModelId = sNewNodeId; + aDataPoint.mpShape = std::make_shared<Shape>(); + aDataPoint.mpShape->setTextBody(std::make_shared<TextBody>()); + TextRunPtr pTextRun = std::make_shared<TextRun>(); + pTextRun->getText() = rText; + aDataPoint.mpShape->getTextBody()->addParagraph().addRun(pTextRun); + + OUString sDataSibling; + for (const auto& aCxn : maConnections) + if (aCxn.mnType == XML_parOf && aCxn.msSourceId == rDataRoot.msModelId) + sDataSibling = aCxn.msDestId; + + OUString sPresSibling; + for (const auto& aCxn : maConnections) + if (aCxn.mnType == XML_presOf && aCxn.msSourceId == sDataSibling) + sPresSibling = aCxn.msDestId; + + dgm::Point aPresPoint; + aPresPoint.mnType = XML_pres; + aPresPoint.msModelId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8); + aPresPoint.mpShape = std::make_shared<Shape>(); + aPresPoint.msPresentationAssociationId = aDataPoint.msModelId; + if (!sPresSibling.isEmpty()) + { + // no idea where to get these values from, so copy from previous sibling + const dgm::Point* pSiblingPoint = maPointNameMap[sPresSibling]; + aPresPoint.msPresentationLayoutName = pSiblingPoint->msPresentationLayoutName; + aPresPoint.msPresentationLayoutStyleLabel = pSiblingPoint->msPresentationLayoutStyleLabel; + aPresPoint.mnLayoutStyleIndex = pSiblingPoint->mnLayoutStyleIndex; + aPresPoint.mnLayoutStyleCount = pSiblingPoint->mnLayoutStyleCount; + } + + addConnection(XML_parOf, rDataRoot.msModelId, aDataPoint.msModelId); + addConnection(XML_presParOf, sPresRoot, aPresPoint.msModelId); + addConnection(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(); + return sNewNodeId; +} + +bool DiagramData::removeNode(const OUString& rNodeId) +{ + // check if it doesn't have children + for (const auto& aCxn : maConnections) + if (aCxn.mnType == XML_parOf && aCxn.msSourceId == rNodeId) + { + SAL_WARN("oox.drawingml", "Node has children - can't be removed"); + return false; + } + + dgm::Connection aParCxn; + for (const auto& aCxn : maConnections) + if (aCxn.mnType == XML_parOf && aCxn.msDestId == rNodeId) + aParCxn = aCxn; + + std::unordered_set<OUString> aIdsToRemove; + aIdsToRemove.insert(rNodeId); + if (!aParCxn.msParTransId.isEmpty()) + aIdsToRemove.insert(aParCxn.msParTransId); + if (!aParCxn.msSibTransId.isEmpty()) + aIdsToRemove.insert(aParCxn.msSibTransId); + + for (const dgm::Point& rPoint : maPoints) + if (aIdsToRemove.count(rPoint.msPresentationAssociationId)) + aIdsToRemove.insert(rPoint.msModelId); + + // insert also transition nodes + for (const auto& aCxn : maConnections) + if (aIdsToRemove.count(aCxn.msSourceId) || aIdsToRemove.count(aCxn.msDestId)) + if (!aCxn.msPresId.isEmpty()) + aIdsToRemove.insert(aCxn.msPresId); + + // remove connections + maConnections.erase(std::remove_if(maConnections.begin(), maConnections.end(), + [aIdsToRemove](const dgm::Connection& rCxn) { + return aIdsToRemove.count(rCxn.msSourceId) || aIdsToRemove.count(rCxn.msDestId); + }), + maConnections.end()); + + // remove data and presentation nodes + maPoints.erase(std::remove_if(maPoints.begin(), maPoints.end(), + [aIdsToRemove](const dgm::Point& rPoint) { + return aIdsToRemove.count(rPoint.msModelId); + }), + maPoints.end()); + + // TODO: fix source/dest order + + build(); + return true; +} + +#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( const OUString& rNodeName, + const dgm::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.mnType == XML_parOf && + rNodeName == elem.msDestId ) + { + return calcDepth(elem.msSourceId, rCnx) + 1; + } + } + + return 0; +} + +void DiagramData::build() +{ + // 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 + dgm::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.mnType ) + { + 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 + + // does currpoint have any text set? + if( point.mpShape && + point.mpShape->getTextBody() && + !point.mpShape->getTextBody()->isEmpty() ) + { +#ifdef DEBUG_OOX_DIAGRAM + static sal_Int32 nCount=0; + output << "\t" + << "textNode" << nCount + << " [" + << "label=\"" + << OUStringToOString( + point.mpShape->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); + } + } + + const dgm::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.mnType == 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: */ |