Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / oox / source / drawingml / diagram / datamodel.cxx
blobfcad85bd3d6a3f9eeba421845e0ae9568887cf60
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "datamodel.hxx"
21 #include <rtl/ustrbuf.hxx>
22 #include <sal/log.hxx>
23 #include <drawingml/customshapeproperties.hxx>
24 #include <drawingml/fillproperties.hxx>
25 #include <drawingml/textbody.hxx>
26 #include <drawingml/textparagraph.hxx>
27 #include <drawingml/textrun.hxx>
28 #include <oox/token/namespaces.hxx>
29 #include <svx/DiagramDataInterface.hxx>
30 #include <comphelper/xmltools.hxx>
32 #include <unordered_set>
33 #include <iostream>
34 #include <fstream>
36 using namespace ::com::sun::star;
38 namespace oox { namespace drawingml {
40 namespace dgm {
42 void Connection::dump() const
44 SAL_INFO(
45 "oox.drawingml",
46 "cnx modelId " << msModelId << ", srcId " << msSourceId << ", dstId "
47 << msDestId << ", parTransId " << msParTransId << ", presId "
48 << msPresId << ", sibTransId " << msSibTransId << ", srcOrd "
49 << mnSourceOrder << ", dstOrd " << mnDestOrder);
52 void Point::dump() const
54 SAL_INFO(
55 "oox.drawingml",
56 "pt text " << mpShape.get() << ", cnxId " << msCnxId << ", modelId "
57 << msModelId << ", type " << mnType);
60 } // dgm namespace
62 DiagramData::DiagramData() :
63 mpFillProperties( new FillProperties )
67 const dgm::Point* DiagramData::getRootPoint() const
69 for (const auto & aCurrPoint : maPoints)
70 if (aCurrPoint.mnType == XML_doc)
71 return &aCurrPoint;
73 SAL_WARN("oox.drawingml", "No root point");
74 return nullptr;
77 void DiagramData::dump() const
79 SAL_INFO("oox.drawingml", "Dgm: DiagramData # of cnx: " << maConnections.size() );
80 for (const auto& rConnection : maConnections)
81 rConnection.dump();
83 SAL_INFO("oox.drawingml", "Dgm: DiagramData # of pt: " << maPoints.size() );
84 for (const auto& rPoint : maPoints)
85 rPoint.dump();
88 void DiagramData::getChildrenString(OUStringBuffer& rBuf, const dgm::Point* pPoint, sal_Int32 nLevel) const
90 if (!pPoint)
91 return;
93 if (nLevel > 0)
95 for (sal_Int32 i = 0; i < nLevel-1; i++)
96 rBuf.append('\t');
97 rBuf.append('+');
98 rBuf.append(' ');
99 rBuf.append(pPoint->mpShape->getTextBody()->toString());
100 rBuf.append('\n');
103 std::vector<const dgm::Point*> aChildren;
104 for (const auto& rCxn : maConnections)
105 if (rCxn.mnType == XML_parOf && rCxn.msSourceId == pPoint->msModelId)
107 if (rCxn.mnSourceOrder >= static_cast<sal_Int32>(aChildren.size()))
108 aChildren.resize(rCxn.mnSourceOrder + 1);
109 const auto pChild = maPointNameMap.find(rCxn.msDestId);
110 if (pChild != maPointNameMap.end())
111 aChildren[rCxn.mnSourceOrder] = pChild->second;
114 for (auto pChild : aChildren)
115 getChildrenString(rBuf, pChild, nLevel + 1);
118 OUString DiagramData::getString() const
120 OUStringBuffer aBuf;
121 const dgm::Point* pPoint = getRootPoint();
122 getChildrenString(aBuf, pPoint, 0);
123 return aBuf.makeStringAndClear();
126 std::vector<std::pair<OUString, OUString>> DiagramData::getChildren(const OUString& rParentId) const
128 const OUString sModelId = rParentId.isEmpty() ? getRootPoint()->msModelId : rParentId;
129 std::vector<std::pair<OUString, OUString>> aChildren;
130 for (const auto& rCxn : maConnections)
131 if (rCxn.mnType == XML_parOf && rCxn.msSourceId == sModelId)
133 if (rCxn.mnSourceOrder >= static_cast<sal_Int32>(aChildren.size()))
134 aChildren.resize(rCxn.mnSourceOrder + 1);
135 const auto pChild = maPointNameMap.find(rCxn.msDestId);
136 if (pChild != maPointNameMap.end())
137 aChildren[rCxn.mnSourceOrder] = std::make_pair(
138 pChild->second->msModelId,
139 pChild->second->mpShape->getTextBody()->toString());
142 // HACK: empty items shouldn't appear there
143 aChildren.erase(std::remove_if(aChildren.begin(), aChildren.end(),
144 [](const std::pair<OUString, OUString>& aItem) { return aItem.first.isEmpty(); }),
145 aChildren.end());
147 return aChildren;
150 void DiagramData::addConnection(sal_Int32 nType, const OUString& sSourceId, const OUString& sDestId)
152 sal_Int32 nMaxOrd = -1;
153 for (const auto& aCxn : maConnections)
154 if (aCxn.mnType == nType && aCxn.msSourceId == sSourceId)
155 nMaxOrd = std::max(nMaxOrd, aCxn.mnSourceOrder);
157 dgm::Connection& rCxn = maConnections.emplace_back();
158 rCxn.mnType = nType;
159 rCxn.msSourceId = sSourceId;
160 rCxn.msDestId = sDestId;
161 rCxn.mnSourceOrder = nMaxOrd + 1;
164 OUString DiagramData::addNode(const OUString& rText)
166 const dgm::Point& rDataRoot = *getRootPoint();
167 OUString sPresRoot;
168 for (const auto& aCxn : maConnections)
169 if (aCxn.mnType == XML_presOf && aCxn.msSourceId == rDataRoot.msModelId)
170 sPresRoot = aCxn.msDestId;
172 if (sPresRoot.isEmpty())
173 return OUString();
175 OUString sNewNodeId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8);
177 dgm::Point aDataPoint;
178 aDataPoint.mnType = XML_node;
179 aDataPoint.msModelId = sNewNodeId;
180 aDataPoint.mpShape.reset(new Shape());
181 aDataPoint.mpShape->setTextBody(std::make_shared<TextBody>());
182 TextRunPtr pTextRun(new TextRun());
183 pTextRun->getText() = rText;
184 aDataPoint.mpShape->getTextBody()->addParagraph().addRun(pTextRun);
186 OUString sDataSibling;
187 for (const auto& aCxn : maConnections)
188 if (aCxn.mnType == XML_parOf && aCxn.msSourceId == rDataRoot.msModelId)
189 sDataSibling = aCxn.msDestId;
191 OUString sPresSibling;
192 for (const auto& aCxn : maConnections)
193 if (aCxn.mnType == XML_presOf && aCxn.msSourceId == sDataSibling)
194 sPresSibling = aCxn.msDestId;
196 dgm::Point aPresPoint;
197 aPresPoint.mnType = XML_pres;
198 aPresPoint.msModelId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8);
199 aPresPoint.mpShape.reset(new Shape());
200 aPresPoint.msPresentationAssociationId = aDataPoint.msModelId;
201 if (!sPresSibling.isEmpty())
203 // no idea where to get these values from, so copy from previous sibling
204 const dgm::Point* pSiblingPoint = maPointNameMap[sPresSibling];
205 aPresPoint.msPresentationLayoutName = pSiblingPoint->msPresentationLayoutName;
206 aPresPoint.msPresentationLayoutStyleLabel = pSiblingPoint->msPresentationLayoutStyleLabel;
207 aPresPoint.mnLayoutStyleIndex = pSiblingPoint->mnLayoutStyleIndex;
208 aPresPoint.mnLayoutStyleCount = pSiblingPoint->mnLayoutStyleCount;
211 addConnection(XML_parOf, rDataRoot.msModelId, aDataPoint.msModelId);
212 addConnection(XML_presParOf, sPresRoot, aPresPoint.msModelId);
213 addConnection(XML_presOf, aDataPoint.msModelId, aPresPoint.msModelId);
215 // adding at the end, so that references are not invalidated in between
216 maPoints.push_back(aDataPoint);
217 maPoints.push_back(aPresPoint);
219 build();
220 return sNewNodeId;
223 bool DiagramData::removeNode(const OUString& rNodeId)
225 // check if it doesn't have children
226 for (const auto& aCxn : maConnections)
227 if (aCxn.mnType == XML_parOf && aCxn.msSourceId == rNodeId)
229 SAL_WARN("oox.drawingml", "Node has children - can't be removed");
230 return false;
233 dgm::Connection aParCxn;
234 for (const auto& aCxn : maConnections)
235 if (aCxn.mnType == XML_parOf && aCxn.msDestId == rNodeId)
236 aParCxn = aCxn;
238 std::unordered_set<OUString> aIdsToRemove;
239 aIdsToRemove.insert(rNodeId);
240 if (!aParCxn.msParTransId.isEmpty())
241 aIdsToRemove.insert(aParCxn.msParTransId);
242 if (!aParCxn.msSibTransId.isEmpty())
243 aIdsToRemove.insert(aParCxn.msSibTransId);
245 for (const dgm::Point& rPoint : maPoints)
246 if (aIdsToRemove.count(rPoint.msPresentationAssociationId))
247 aIdsToRemove.insert(rPoint.msModelId);
249 // insert also transition nodes
250 for (const auto& aCxn : maConnections)
251 if (aIdsToRemove.count(aCxn.msSourceId) || aIdsToRemove.count(aCxn.msDestId))
252 if (!aCxn.msPresId.isEmpty())
253 aIdsToRemove.insert(aCxn.msPresId);
255 // remove connections
256 maConnections.erase(std::remove_if(maConnections.begin(), maConnections.end(),
257 [aIdsToRemove](const dgm::Connection& rCxn) {
258 return aIdsToRemove.count(rCxn.msSourceId) || aIdsToRemove.count(rCxn.msDestId);
260 maConnections.end());
262 // remove data and presentation nodes
263 maPoints.erase(std::remove_if(maPoints.begin(), maPoints.end(),
264 [aIdsToRemove](const dgm::Point& rPoint) {
265 return aIdsToRemove.count(rPoint.msModelId);
267 maPoints.end());
269 // TODO: fix source/dest order
271 build();
272 return true;
275 #ifdef DEBUG_OOX_DIAGRAM
276 OString normalizeDotName( const OUString& rStr )
278 OUStringBuffer aBuf;
279 aBuf.append('N');
281 const sal_Int32 nLen(rStr.getLength());
282 sal_Int32 nCurrIndex(0);
283 while( nCurrIndex < nLen )
285 const sal_Int32 aChar=rStr.iterateCodePoints(&nCurrIndex);
286 if( aChar != '-' && aChar != '{' && aChar != '}' )
287 aBuf.append((sal_Unicode)aChar);
290 return OUStringToOString(aBuf.makeStringAndClear(),
291 RTL_TEXTENCODING_UTF8);
293 #endif
295 static sal_Int32 calcDepth( const OUString& rNodeName,
296 const dgm::Connections& rCnx )
298 // find length of longest path in 'isChild' graph, ending with rNodeName
299 for (auto const& elem : rCnx)
301 if( !elem.msParTransId.isEmpty() &&
302 !elem.msSibTransId.isEmpty() &&
303 !elem.msSourceId.isEmpty() &&
304 !elem.msDestId.isEmpty() &&
305 elem.mnType == XML_parOf &&
306 rNodeName == elem.msDestId )
308 return calcDepth(elem.msSourceId, rCnx) + 1;
312 return 0;
315 void DiagramData::build()
317 // build name-object maps
318 maPointNameMap.clear();
319 maPointsPresNameMap.clear();
320 maConnectionNameMap.clear();
321 maPresOfNameMap.clear();
323 #ifdef DEBUG_OOX_DIAGRAM
324 std::ofstream output("tree.dot");
326 output << "digraph datatree {" << std::endl;
327 #endif
328 dgm::Points& rPoints = getPoints();
329 for (auto & point : rPoints)
331 #ifdef DEBUG_OOX_DIAGRAM
332 output << "\t"
333 << normalizeDotName(point.msModelId).getStr()
334 << "[";
336 if( !point.msPresentationLayoutName.isEmpty() )
337 output << "label=\""
338 << OUStringToOString(
339 point.msPresentationLayoutName,
340 RTL_TEXTENCODING_UTF8).getStr() << "\", ";
341 else
342 output << "label=\""
343 << OUStringToOString(
344 point.msModelId,
345 RTL_TEXTENCODING_UTF8).getStr() << "\", ";
347 switch( point.mnType )
349 case XML_doc: output << "style=filled, color=red"; break;
350 case XML_asst: output << "style=filled, color=green"; break;
351 default:
352 case XML_node: output << "style=filled, color=blue"; break;
353 case XML_pres: output << "style=filled, color=yellow"; break;
354 case XML_parTrans: output << "color=grey"; break;
355 case XML_sibTrans: output << " "; break;
358 output << "];" << std::endl;
359 #endif
361 // does currpoint have any text set?
362 if( point.mpShape &&
363 point.mpShape->getTextBody() &&
364 !point.mpShape->getTextBody()->isEmpty() )
366 #ifdef DEBUG_OOX_DIAGRAM
367 static sal_Int32 nCount=0;
368 output << "\t"
369 << "textNode" << nCount
370 << " ["
371 << "label=\""
372 << OUStringToOString(
373 point.mpShape->getTextBody()->toString(),
374 RTL_TEXTENCODING_UTF8).getStr()
375 << "\"" << "];" << std::endl;
376 output << "\t"
377 << normalizeDotName(point.msModelId).getStr()
378 << " -> "
379 << "textNode" << nCount++
380 << ";" << std::endl;
381 #endif
384 const bool bInserted1 = getPointNameMap().insert(
385 std::make_pair(point.msModelId,&point)).second;
387 SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique point model id");
389 if( !point.msPresentationLayoutName.isEmpty() )
391 DiagramData::PointsNameMap::value_type::second_type& rVec=
392 getPointsPresNameMap()[point.msPresentationLayoutName];
393 rVec.push_back(&point);
397 const dgm::Connections& rConnections = getConnections();
398 for (auto const& connection : rConnections)
400 #ifdef DEBUG_OOX_DIAGRAM
401 if( !connection.msParTransId.isEmpty() ||
402 !connection.msSibTransId.isEmpty() )
404 if( !connection.msSourceId.isEmpty() ||
405 !connection.msDestId.isEmpty() )
407 output << "\t"
408 << normalizeDotName(connection.msSourceId).getStr()
409 << " -> "
410 << normalizeDotName(connection.msParTransId).getStr()
411 << " -> "
412 << normalizeDotName(connection.msSibTransId).getStr()
413 << " -> "
414 << normalizeDotName(connection.msDestId).getStr()
415 << " [style=dotted,"
416 << ((connection.mnType == XML_presOf) ? " color=red, " : ((connection.mnType == XML_presParOf) ? " color=green, " : " "))
417 << "label=\""
418 << OUStringToOString(connection.msModelId,
419 RTL_TEXTENCODING_UTF8 ).getStr()
420 << "\"];" << std::endl;
422 else
424 output << "\t"
425 << normalizeDotName(connection.msParTransId).getStr()
426 << " -> "
427 << normalizeDotName(connection.msSibTransId).getStr()
428 << " ["
429 << ((connection.mnType == XML_presOf) ? " color=red, " : ((connection.mnType == XML_presParOf) ? " color=green, " : " "))
430 << "label=\""
431 << OUStringToOString(connection.msModelId,
432 RTL_TEXTENCODING_UTF8 ).getStr()
433 << "\"];" << std::endl;
436 else if( !connection.msSourceId.isEmpty() ||
437 !connection.msDestId.isEmpty() )
438 output << "\t"
439 << normalizeDotName(connection.msSourceId).getStr()
440 << " -> "
441 << normalizeDotName(connection.msDestId).getStr()
442 << " [label=\""
443 << OUStringToOString(connection.msModelId,
444 RTL_TEXTENCODING_UTF8 ).getStr()
445 << ((connection.mnType == XML_presOf) ? "\", color=red]" : ((connection.mnType == XML_presParOf) ? "\", color=green]" : "\"]"))
446 << ";" << std::endl;
447 #endif
449 const bool bInserted1 = getConnectionNameMap().insert(
450 std::make_pair(connection.msModelId,&connection)).second;
452 SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique connection model id");
454 if( connection.mnType == XML_presOf )
456 DiagramData::StringMap::value_type::second_type& rVec = getPresOfNameMap()[connection.msDestId];
457 rVec[connection.mnDestOrder] = { connection.msSourceId, sal_Int32(0) };
461 // assign outline levels
462 DiagramData::StringMap& rStringMap = getPresOfNameMap();
463 for (auto & elemPresOf : rStringMap)
465 for (auto & elem : elemPresOf.second)
467 const sal_Int32 nDepth = calcDepth(elem.second.msSourceId, getConnections());
468 elem.second.mnDepth = nDepth != 0 ? nDepth : -1;
471 #ifdef DEBUG_OOX_DIAGRAM
472 output << "}" << std::endl;
473 #endif
478 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */