LibreOffice Module oox (master)  1
datamodel.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include "datamodel.hxx"
21 #include <rtl/ustrbuf.hxx>
22 #include <sal/log.hxx>
24 #include <drawingml/textbody.hxx>
26 #include <drawingml/textrun.hxx>
27 #include <oox/drawingml/shape.hxx>
28 #include <comphelper/xmltools.hxx>
29 
30 #include <unordered_set>
31 #include <fstream>
32 
33 using namespace ::com::sun::star;
34 
35 namespace oox::drawingml {
36 
37 namespace dgm {
38 
39 void Connection::dump() const
40 {
41  SAL_INFO(
42  "oox.drawingml",
43  "cnx modelId " << msModelId << ", srcId " << msSourceId << ", dstId "
44  << msDestId << ", parTransId " << msParTransId << ", presId "
45  << msPresId << ", sibTransId " << msSibTransId << ", srcOrd "
46  << mnSourceOrder << ", dstOrd " << mnDestOrder);
47 }
48 
49 void Point::dump() const
50 {
51  SAL_INFO(
52  "oox.drawingml",
53  "pt text " << mpShape.get() << ", cnxId " << msCnxId << ", modelId "
54  << msModelId << ", type " << mnType);
55 }
56 
57 } // oox::drawingml::dgm namespace
58 
60  mpFillProperties( std::make_shared<FillProperties>() )
61 {
62 }
63 
65 {
66  for (const auto & aCurrPoint : maPoints)
67  if (aCurrPoint.mnType == XML_doc)
68  return &aCurrPoint;
69 
70  SAL_WARN("oox.drawingml", "No root point");
71  return nullptr;
72 }
73 
74 void DiagramData::dump() const
75 {
76  SAL_INFO("oox.drawingml", "Dgm: DiagramData # of cnx: " << maConnections.size() );
77  for (const auto& rConnection : maConnections)
78  rConnection.dump();
79 
80  SAL_INFO("oox.drawingml", "Dgm: DiagramData # of pt: " << maPoints.size() );
81  for (const auto& rPoint : maPoints)
82  rPoint.dump();
83 }
84 
85 void DiagramData::getChildrenString(OUStringBuffer& rBuf, const dgm::Point* pPoint, sal_Int32 nLevel) const
86 {
87  if (!pPoint)
88  return;
89 
90  if (nLevel > 0)
91  {
92  for (sal_Int32 i = 0; i < nLevel-1; i++)
93  rBuf.append('\t');
94  rBuf.append('+');
95  rBuf.append(' ');
96  rBuf.append(pPoint->mpShape->getTextBody()->toString());
97  rBuf.append('\n');
98  }
99 
100  std::vector<const dgm::Point*> aChildren;
101  for (const auto& rCxn : maConnections)
102  if (rCxn.mnType == XML_parOf && rCxn.msSourceId == pPoint->msModelId)
103  {
104  if (rCxn.mnSourceOrder >= static_cast<sal_Int32>(aChildren.size()))
105  aChildren.resize(rCxn.mnSourceOrder + 1);
106  const auto pChild = maPointNameMap.find(rCxn.msDestId);
107  if (pChild != maPointNameMap.end())
108  aChildren[rCxn.mnSourceOrder] = pChild->second;
109  }
110 
111  for (auto pChild : aChildren)
112  getChildrenString(rBuf, pChild, nLevel + 1);
113 }
114 
115 OUString DiagramData::getString() const
116 {
117  OUStringBuffer aBuf;
118  const dgm::Point* pPoint = getRootPoint();
119  getChildrenString(aBuf, pPoint, 0);
120  return aBuf.makeStringAndClear();
121 }
122 
123 std::vector<std::pair<OUString, OUString>> DiagramData::getChildren(const OUString& rParentId) const
124 {
125  const OUString sModelId = rParentId.isEmpty() ? getRootPoint()->msModelId : rParentId;
126  std::vector<std::pair<OUString, OUString>> aChildren;
127  for (const auto& rCxn : maConnections)
128  if (rCxn.mnType == XML_parOf && rCxn.msSourceId == sModelId)
129  {
130  if (rCxn.mnSourceOrder >= static_cast<sal_Int32>(aChildren.size()))
131  aChildren.resize(rCxn.mnSourceOrder + 1);
132  const auto pChild = maPointNameMap.find(rCxn.msDestId);
133  if (pChild != maPointNameMap.end())
134  aChildren[rCxn.mnSourceOrder] = std::make_pair(
135  pChild->second->msModelId,
136  pChild->second->mpShape->getTextBody()->toString());
137  }
138 
139  // HACK: empty items shouldn't appear there
140  aChildren.erase(std::remove_if(aChildren.begin(), aChildren.end(),
141  [](const std::pair<OUString, OUString>& aItem) { return aItem.first.isEmpty(); }),
142  aChildren.end());
143 
144  return aChildren;
145 }
146 
147 void DiagramData::addConnection(sal_Int32 nType, const OUString& sSourceId, const OUString& sDestId)
148 {
149  sal_Int32 nMaxOrd = -1;
150  for (const auto& aCxn : maConnections)
151  if (aCxn.mnType == nType && aCxn.msSourceId == sSourceId)
152  nMaxOrd = std::max(nMaxOrd, aCxn.mnSourceOrder);
153 
154  dgm::Connection& rCxn = maConnections.emplace_back();
155  rCxn.mnType = nType;
156  rCxn.msSourceId = sSourceId;
157  rCxn.msDestId = sDestId;
158  rCxn.mnSourceOrder = nMaxOrd + 1;
159 }
160 
161 OUString DiagramData::addNode(const OUString& rText)
162 {
163  const dgm::Point& rDataRoot = *getRootPoint();
164  OUString sPresRoot;
165  for (const auto& aCxn : maConnections)
166  if (aCxn.mnType == XML_presOf && aCxn.msSourceId == rDataRoot.msModelId)
167  sPresRoot = aCxn.msDestId;
168 
169  if (sPresRoot.isEmpty())
170  return OUString();
171 
172  OUString sNewNodeId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8);
173 
174  dgm::Point aDataPoint;
175  aDataPoint.mnType = XML_node;
176  aDataPoint.msModelId = sNewNodeId;
177  aDataPoint.mpShape = std::make_shared<Shape>();
178  aDataPoint.mpShape->setTextBody(std::make_shared<TextBody>());
179  TextRunPtr pTextRun = std::make_shared<TextRun>();
180  pTextRun->getText() = rText;
181  aDataPoint.mpShape->getTextBody()->addParagraph().addRun(pTextRun);
182 
183  OUString sDataSibling;
184  for (const auto& aCxn : maConnections)
185  if (aCxn.mnType == XML_parOf && aCxn.msSourceId == rDataRoot.msModelId)
186  sDataSibling = aCxn.msDestId;
187 
188  OUString sPresSibling;
189  for (const auto& aCxn : maConnections)
190  if (aCxn.mnType == XML_presOf && aCxn.msSourceId == sDataSibling)
191  sPresSibling = aCxn.msDestId;
192 
193  dgm::Point aPresPoint;
194  aPresPoint.mnType = XML_pres;
195  aPresPoint.msModelId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8);
196  aPresPoint.mpShape = std::make_shared<Shape>();
197  aPresPoint.msPresentationAssociationId = aDataPoint.msModelId;
198  if (!sPresSibling.isEmpty())
199  {
200  // no idea where to get these values from, so copy from previous sibling
201  const dgm::Point* pSiblingPoint = maPointNameMap[sPresSibling];
202  aPresPoint.msPresentationLayoutName = pSiblingPoint->msPresentationLayoutName;
204  aPresPoint.mnLayoutStyleIndex = pSiblingPoint->mnLayoutStyleIndex;
205  aPresPoint.mnLayoutStyleCount = pSiblingPoint->mnLayoutStyleCount;
206  }
207 
208  addConnection(XML_parOf, rDataRoot.msModelId, aDataPoint.msModelId);
209  addConnection(XML_presParOf, sPresRoot, aPresPoint.msModelId);
210  addConnection(XML_presOf, aDataPoint.msModelId, aPresPoint.msModelId);
211 
212  // adding at the end, so that references are not invalidated in between
213  maPoints.push_back(aDataPoint);
214  maPoints.push_back(aPresPoint);
215 
216  build();
217  return sNewNodeId;
218 }
219 
220 bool DiagramData::removeNode(const OUString& rNodeId)
221 {
222  // check if it doesn't have children
223  for (const auto& aCxn : maConnections)
224  if (aCxn.mnType == XML_parOf && aCxn.msSourceId == rNodeId)
225  {
226  SAL_WARN("oox.drawingml", "Node has children - can't be removed");
227  return false;
228  }
229 
230  dgm::Connection aParCxn;
231  for (const auto& aCxn : maConnections)
232  if (aCxn.mnType == XML_parOf && aCxn.msDestId == rNodeId)
233  aParCxn = aCxn;
234 
235  std::unordered_set<OUString> aIdsToRemove;
236  aIdsToRemove.insert(rNodeId);
237  if (!aParCxn.msParTransId.isEmpty())
238  aIdsToRemove.insert(aParCxn.msParTransId);
239  if (!aParCxn.msSibTransId.isEmpty())
240  aIdsToRemove.insert(aParCxn.msSibTransId);
241 
242  for (const dgm::Point& rPoint : maPoints)
243  if (aIdsToRemove.count(rPoint.msPresentationAssociationId))
244  aIdsToRemove.insert(rPoint.msModelId);
245 
246  // insert also transition nodes
247  for (const auto& aCxn : maConnections)
248  if (aIdsToRemove.count(aCxn.msSourceId) || aIdsToRemove.count(aCxn.msDestId))
249  if (!aCxn.msPresId.isEmpty())
250  aIdsToRemove.insert(aCxn.msPresId);
251 
252  // remove connections
253  maConnections.erase(std::remove_if(maConnections.begin(), maConnections.end(),
254  [aIdsToRemove](const dgm::Connection& rCxn) {
255  return aIdsToRemove.count(rCxn.msSourceId) || aIdsToRemove.count(rCxn.msDestId);
256  }),
257  maConnections.end());
258 
259  // remove data and presentation nodes
260  maPoints.erase(std::remove_if(maPoints.begin(), maPoints.end(),
261  [aIdsToRemove](const dgm::Point& rPoint) {
262  return aIdsToRemove.count(rPoint.msModelId);
263  }),
264  maPoints.end());
265 
266  // TODO: fix source/dest order
267 
268  build();
269  return true;
270 }
271 
272 #ifdef DEBUG_OOX_DIAGRAM
273 OString normalizeDotName( const OUString& rStr )
274 {
275  OUStringBuffer aBuf;
276  aBuf.append('N');
277 
278  const sal_Int32 nLen(rStr.getLength());
279  sal_Int32 nCurrIndex(0);
280  while( nCurrIndex < nLen )
281  {
282  const sal_Int32 aChar=rStr.iterateCodePoints(&nCurrIndex);
283  if( aChar != '-' && aChar != '{' && aChar != '}' )
284  aBuf.append((sal_Unicode)aChar);
285  }
286 
287  return OUStringToOString(aBuf.makeStringAndClear(),
288  RTL_TEXTENCODING_UTF8);
289 }
290 #endif
291 
292 static sal_Int32 calcDepth( std::u16string_view rNodeName,
293  const dgm::Connections& rCnx )
294 {
295  // find length of longest path in 'isChild' graph, ending with rNodeName
296  for (auto const& elem : rCnx)
297  {
298  if( !elem.msParTransId.isEmpty() &&
299  !elem.msSibTransId.isEmpty() &&
300  !elem.msSourceId.isEmpty() &&
301  !elem.msDestId.isEmpty() &&
302  elem.mnType == XML_parOf &&
303  rNodeName == elem.msDestId )
304  {
305  return calcDepth(elem.msSourceId, rCnx) + 1;
306  }
307  }
308 
309  return 0;
310 }
311 
313 {
314  // build name-object maps
315  maPointNameMap.clear();
316  maPointsPresNameMap.clear();
317  maConnectionNameMap.clear();
318  maPresOfNameMap.clear();
319 
320 #ifdef DEBUG_OOX_DIAGRAM
321  std::ofstream output("tree.dot");
322 
323  output << "digraph datatree {" << std::endl;
324 #endif
325  dgm::Points& rPoints = getPoints();
326  for (auto & point : rPoints)
327  {
328 #ifdef DEBUG_OOX_DIAGRAM
329  output << "\t"
330  << normalizeDotName(point.msModelId).getStr()
331  << "[";
332 
333  if( !point.msPresentationLayoutName.isEmpty() )
334  output << "label=\""
336  point.msPresentationLayoutName,
337  RTL_TEXTENCODING_UTF8).getStr() << "\", ";
338  else
339  output << "label=\""
341  point.msModelId,
342  RTL_TEXTENCODING_UTF8).getStr() << "\", ";
343 
344  switch( point.mnType )
345  {
346  case XML_doc: output << "style=filled, color=red"; break;
347  case XML_asst: output << "style=filled, color=green"; break;
348  default:
349  case XML_node: output << "style=filled, color=blue"; break;
350  case XML_pres: output << "style=filled, color=yellow"; break;
351  case XML_parTrans: output << "color=grey"; break;
352  case XML_sibTrans: output << " "; break;
353  }
354 
355  output << "];" << std::endl;
356 #endif
357 
358  // does currpoint have any text set?
359  if( point.mpShape &&
360  point.mpShape->getTextBody() &&
361  !point.mpShape->getTextBody()->isEmpty() )
362  {
363 #ifdef DEBUG_OOX_DIAGRAM
364  static sal_Int32 nCount=0;
365  output << "\t"
366  << "textNode" << nCount
367  << " ["
368  << "label=\""
370  point.mpShape->getTextBody()->toString(),
371  RTL_TEXTENCODING_UTF8).getStr()
372  << "\"" << "];" << std::endl;
373  output << "\t"
374  << normalizeDotName(point.msModelId).getStr()
375  << " -> "
376  << "textNode" << nCount++
377  << ";" << std::endl;
378 #endif
379  }
380 
381  const bool bInserted1 = getPointNameMap().insert(
382  std::make_pair(point.msModelId,&point)).second;
383 
384  SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique point model id");
385 
386  if( !point.msPresentationLayoutName.isEmpty() )
387  {
388  DiagramData::PointsNameMap::value_type::second_type& rVec=
389  getPointsPresNameMap()[point.msPresentationLayoutName];
390  rVec.push_back(&point);
391  }
392  }
393 
394  const dgm::Connections& rConnections = getConnections();
395  for (auto const& connection : rConnections)
396  {
397 #ifdef DEBUG_OOX_DIAGRAM
398  if( !connection.msParTransId.isEmpty() ||
399  !connection.msSibTransId.isEmpty() )
400  {
401  if( !connection.msSourceId.isEmpty() ||
402  !connection.msDestId.isEmpty() )
403  {
404  output << "\t"
405  << normalizeDotName(connection.msSourceId).getStr()
406  << " -> "
407  << normalizeDotName(connection.msParTransId).getStr()
408  << " -> "
409  << normalizeDotName(connection.msSibTransId).getStr()
410  << " -> "
411  << normalizeDotName(connection.msDestId).getStr()
412  << " [style=dotted,"
413  << ((connection.mnType == XML_presOf) ? " color=red, " : ((connection.mnType == XML_presParOf) ? " color=green, " : " "))
414  << "label=\""
415  << OUStringToOString(connection.msModelId,
416  RTL_TEXTENCODING_UTF8 ).getStr()
417  << "\"];" << std::endl;
418  }
419  else
420  {
421  output << "\t"
422  << normalizeDotName(connection.msParTransId).getStr()
423  << " -> "
424  << normalizeDotName(connection.msSibTransId).getStr()
425  << " ["
426  << ((connection.mnType == XML_presOf) ? " color=red, " : ((connection.mnType == XML_presParOf) ? " color=green, " : " "))
427  << "label=\""
428  << OUStringToOString(connection.msModelId,
429  RTL_TEXTENCODING_UTF8 ).getStr()
430  << "\"];" << std::endl;
431  }
432  }
433  else if( !connection.msSourceId.isEmpty() ||
434  !connection.msDestId.isEmpty() )
435  output << "\t"
436  << normalizeDotName(connection.msSourceId).getStr()
437  << " -> "
438  << normalizeDotName(connection.msDestId).getStr()
439  << " [label=\""
440  << OUStringToOString(connection.msModelId,
441  RTL_TEXTENCODING_UTF8 ).getStr()
442  << ((connection.mnType == XML_presOf) ? "\", color=red]" : ((connection.mnType == XML_presParOf) ? "\", color=green]" : "\"]"))
443  << ";" << std::endl;
444 #endif
445 
446  const bool bInserted1 = maConnectionNameMap.insert(
447  std::make_pair(connection.msModelId,&connection)).second;
448 
449  SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique connection model id");
450 
451  if( connection.mnType == XML_presOf )
452  {
453  DiagramData::StringMap::value_type::second_type& rVec = getPresOfNameMap()[connection.msDestId];
454  rVec[connection.mnDestOrder] = { connection.msSourceId, sal_Int32(0) };
455  }
456  }
457 
458  // assign outline levels
460  for (auto & elemPresOf : rStringMap)
461  {
462  for (auto & elem : elemPresOf.second)
463  {
464  const sal_Int32 nDepth = calcDepth(elem.second.msSourceId, getConnections());
465  elem.second.mnDepth = nDepth != 0 ? nDepth : -1;
466  }
467  }
468 #ifdef DEBUG_OOX_DIAGRAM
469  output << "}" << std::endl;
470 #endif
471 }
472 
473 }
474 
475 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
StringMap & getPresOfNameMap()
Definition: datamodel.hxx:170
OUString getString() const override
Definition: datamodel.cxx:115
aBuf
def point()
std::shared_ptr< T > make_shared(Args &&...args)
std::shared_ptr< TextRun > TextRunPtr
Definition: textrun.hxx:65
dgm::Points & getPoints()
Definition: datamodel.hxx:168
sal_uInt16 sal_Unicode
int nCount
ConnectionNameMap maConnectionNameMap
Definition: datamodel.hxx:195
OUString msPresentationAssociationId
Definition: datamodel.hxx:106
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
exports com.sun.star. connection
bool removeNode(const OUString &rNodeId) override
Definition: datamodel.cxx:220
dgm::Connections maConnections
Definition: datamodel.hxx:191
OUString addNode(const OUString &rText) override
Definition: datamodel.cxx:161
std::vector< Point > Points
Definition: datamodel.hxx:141
dgm::Connections & getConnections()
Definition: datamodel.hxx:166
OString generateGUIDString()
const dgm::Point * getRootPoint() const
Definition: datamodel.cxx:64
PointsNameMap & getPointsPresNameMap()
Definition: datamodel.hxx:174
#define SAL_WARN_IF(condition, area, stream)
#define SAL_INFO(area, stream)
std::vector< Connection > Connections
Definition: datamodel.hxx:61
#define SAL_WARN(area, stream)
std::vector< std::pair< OUString, OUString > > getChildren(const OUString &rParentId) const override
Definition: datamodel.cxx:123
void getChildrenString(OUStringBuffer &rBuf, const dgm::Point *pPoint, sal_Int32 nLevel) const
Definition: datamodel.cxx:85
std::map< OUString, std::map< sal_Int32, SourceIdAndDepth > > StringMap
Tracks connections: destination id -> {destination order, details} map.
Definition: datamodel.hxx:159
PointsNameMap maPointsPresNameMap
Definition: datamodel.hxx:194
PointNameMap & getPointNameMap()
Definition: datamodel.hxx:172
OUString msPresentationLayoutStyleLabel
Definition: datamodel.hxx:108
static sal_Int32 calcDepth(std::u16string_view rNodeName, const dgm::Connections &rCnx)
Definition: datamodel.cxx:292
void addConnection(sal_Int32 nType, const OUString &sSourceId, const OUString &sDestId)
Definition: datamodel.cxx:147