1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
36 using namespace ::com::sun::star
;
38 namespace oox
{ namespace drawingml
{
42 void Connection::dump() const
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
56 "pt text " << mpShape
.get() << ", cnxId " << msCnxId
<< ", modelId "
57 << msModelId
<< ", type " << mnType
);
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
)
73 SAL_WARN("oox.drawingml", "No root point");
77 void DiagramData::dump() const
79 SAL_INFO("oox.drawingml", "Dgm: DiagramData # of cnx: " << maConnections
.size() );
80 for (const auto& rConnection
: maConnections
)
83 SAL_INFO("oox.drawingml", "Dgm: DiagramData # of pt: " << maPoints
.size() );
84 for (const auto& rPoint
: maPoints
)
88 void DiagramData::getChildrenString(OUStringBuffer
& rBuf
, const dgm::Point
* pPoint
, sal_Int32 nLevel
) const
95 for (sal_Int32 i
= 0; i
< nLevel
-1; i
++)
99 rBuf
.append(pPoint
->mpShape
->getTextBody()->toString());
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
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(); }),
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();
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();
168 for (const auto& aCxn
: maConnections
)
169 if (aCxn
.mnType
== XML_presOf
&& aCxn
.msSourceId
== rDataRoot
.msModelId
)
170 sPresRoot
= aCxn
.msDestId
;
172 if (sPresRoot
.isEmpty())
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
);
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");
233 dgm::Connection aParCxn
;
234 for (const auto& aCxn
: maConnections
)
235 if (aCxn
.mnType
== XML_parOf
&& aCxn
.msDestId
== rNodeId
)
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
);
269 // TODO: fix source/dest order
275 #ifdef DEBUG_OOX_DIAGRAM
276 OString
normalizeDotName( const OUString
& rStr
)
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
);
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;
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
;
328 dgm::Points
& rPoints
= getPoints();
329 for (auto & point
: rPoints
)
331 #ifdef DEBUG_OOX_DIAGRAM
333 << normalizeDotName(point
.msModelId
).getStr()
336 if( !point
.msPresentationLayoutName
.isEmpty() )
338 << OUStringToOString(
339 point
.msPresentationLayoutName
,
340 RTL_TEXTENCODING_UTF8
).getStr() << "\", ";
343 << OUStringToOString(
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;
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
;
361 // does currpoint have any text set?
363 point
.mpShape
->getTextBody() &&
364 !point
.mpShape
->getTextBody()->isEmpty() )
366 #ifdef DEBUG_OOX_DIAGRAM
367 static sal_Int32 nCount
=0;
369 << "textNode" << nCount
372 << OUStringToOString(
373 point
.mpShape
->getTextBody()->toString(),
374 RTL_TEXTENCODING_UTF8
).getStr()
375 << "\"" << "];" << std::endl
;
377 << normalizeDotName(point
.msModelId
).getStr()
379 << "textNode" << nCount
++
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() )
408 << normalizeDotName(connection
.msSourceId
).getStr()
410 << normalizeDotName(connection
.msParTransId
).getStr()
412 << normalizeDotName(connection
.msSibTransId
).getStr()
414 << normalizeDotName(connection
.msDestId
).getStr()
416 << ((connection
.mnType
== XML_presOf
) ? " color=red, " : ((connection
.mnType
== XML_presParOf
) ? " color=green, " : " "))
418 << OUStringToOString(connection
.msModelId
,
419 RTL_TEXTENCODING_UTF8
).getStr()
420 << "\"];" << std::endl
;
425 << normalizeDotName(connection
.msParTransId
).getStr()
427 << normalizeDotName(connection
.msSibTransId
).getStr()
429 << ((connection
.mnType
== XML_presOf
) ? " color=red, " : ((connection
.mnType
== XML_presParOf
) ? " color=green, " : " "))
431 << OUStringToOString(connection
.msModelId
,
432 RTL_TEXTENCODING_UTF8
).getStr()
433 << "\"];" << std::endl
;
436 else if( !connection
.msSourceId
.isEmpty() ||
437 !connection
.msDestId
.isEmpty() )
439 << normalizeDotName(connection
.msSourceId
).getStr()
441 << normalizeDotName(connection
.msDestId
).getStr()
443 << OUStringToOString(connection
.msModelId
,
444 RTL_TEXTENCODING_UTF8
).getStr()
445 << ((connection
.mnType
== XML_presOf
) ? "\", color=red]" : ((connection
.mnType
== XML_presParOf
) ? "\", color=green]" : "\"]"))
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
;
478 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */