xref: /core/oox/source/export/chartexport.cxx (revision 33f644074e498336d97a881be903662c9a6abedf)
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 <oox/token/namespaces.hxx>
21 #include <oox/token/properties.hxx>
22 #include <oox/token/tokens.hxx>
23 #include <oox/core/xmlfilterbase.hxx>
24 #include <oox/export/chartexport.hxx>
25 #include <oox/token/relationship.hxx>
26 #include <oox/export/utils.hxx>
27 #include <drawingml/chart/typegroupconverter.hxx>
28 #include <basegfx/utils/gradienttools.hxx>
29 #include <docmodel/uno/UnoGradientTools.hxx>
30 
31 #include <cstdio>
32 #include <limits>
33 
34 #include <com/sun/star/awt/Gradient2.hpp>
35 #include <com/sun/star/chart/XChartDocument.hpp>
36 #include <com/sun/star/chart/ChartLegendPosition.hpp>
37 #include <com/sun/star/chart/XTwoAxisXSupplier.hpp>
38 #include <com/sun/star/chart/XTwoAxisYSupplier.hpp>
39 #include <com/sun/star/chart/XAxisZSupplier.hpp>
40 #include <com/sun/star/chart/ChartDataRowSource.hpp>
41 #include <com/sun/star/chart/X3DDisplay.hpp>
42 #include <com/sun/star/chart/XStatisticDisplay.hpp>
43 #include <com/sun/star/chart/XSecondAxisTitleSupplier.hpp>
44 #include <com/sun/star/chart/ChartSymbolType.hpp>
45 #include <com/sun/star/chart/ChartAxisMarks.hpp>
46 #include <com/sun/star/chart/ChartAxisLabelPosition.hpp>
47 #include <com/sun/star/chart/ChartAxisPosition.hpp>
48 #include <com/sun/star/chart/ChartSolidType.hpp>
49 #include <com/sun/star/chart/DataLabelPlacement.hpp>
50 #include <com/sun/star/chart/ErrorBarStyle.hpp>
51 #include <com/sun/star/chart/MissingValueTreatment.hpp>
52 #include <com/sun/star/chart/XDiagramPositioning.hpp>
53 #include <com/sun/star/chart/TimeIncrement.hpp>
54 #include <com/sun/star/chart/TimeInterval.hpp>
55 #include <com/sun/star/chart/TimeUnit.hpp>
56 
57 #include <com/sun/star/chart2/RelativePosition.hpp>
58 #include <com/sun/star/chart2/RelativeSize.hpp>
59 #include <com/sun/star/chart2/XChartDocument.hpp>
60 #include <com/sun/star/chart2/XDiagram.hpp>
61 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
62 #include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
63 #include <com/sun/star/chart2/XChartTypeContainer.hpp>
64 #include <com/sun/star/chart2/XDataSeriesContainer.hpp>
65 #include <com/sun/star/chart2/DataPointLabel.hpp>
66 #include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
67 #include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp>
68 #include <com/sun/star/chart2/PieChartSubType.hpp>
69 #include <com/sun/star/chart2/Symbol.hpp>
70 #include <com/sun/star/chart2/data/XDataSource.hpp>
71 #include <com/sun/star/chart2/data/XDataProvider.hpp>
72 #include <com/sun/star/chart2/data/XTextualDataSequence.hpp>
73 #include <com/sun/star/chart2/data/XNumericalDataSequence.hpp>
74 #include <com/sun/star/chart2/data/XLabeledDataSequence.hpp>
75 #include <com/sun/star/chart2/XAnyDescriptionAccess.hpp>
76 #include <com/sun/star/chart2/AxisType.hpp>
77 
78 #include <com/sun/star/beans/XPropertySet.hpp>
79 #include <com/sun/star/container/XNameAccess.hpp>
80 #include <com/sun/star/drawing/XShape.hpp>
81 #include <com/sun/star/drawing/XShapes.hpp>
82 #include <com/sun/star/drawing/FillStyle.hpp>
83 #include <com/sun/star/drawing/LineStyle.hpp>
84 #include <com/sun/star/awt/XBitmap.hpp>
85 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
86 #include <com/sun/star/lang/XServiceName.hpp>
87 
88 #include <com/sun/star/table/CellAddress.hpp>
89 #include <com/sun/star/sheet/XFormulaParser.hpp>
90 #include <com/sun/star/sheet/FormulaToken.hpp>
91 #include <com/sun/star/sheet/AddressConvention.hpp>
92 
93 #include <com/sun/star/container/XNamed.hpp>
94 #include <com/sun/star/embed/XVisualObject.hpp>
95 #include <com/sun/star/embed/Aspects.hpp>
96 
97 #include <comphelper/processfactory.hxx>
98 #include <comphelper/random.hxx>
99 #include <utility>
100 #include <xmloff/SchXMLSeriesHelper.hxx>
101 #include "ColorPropertySet.hxx"
102 
103 #include <svl/numformat.hxx>
104 #include <svl/numuno.hxx>
105 #include <comphelper/diagnose_ex.hxx>
106 #include <sal/log.hxx>
107 
108 #include <set>
109 #include <unordered_set>
110 
111 #include <frozen/bits/defines.h>
112 #include <frozen/bits/elsa_std.h>
113 #include <frozen/unordered_map.h>
114 
115 #include <o3tl/temporary.hxx>
116 #include <o3tl/sorted_vector.hxx>
117 
118 using namespace css;
119 using namespace css::uno;
120 using namespace css::drawing;
121 using namespace ::oox::core;
122 using css::beans::PropertyValue;
123 using css::beans::XPropertySet;
124 using css::container::XNamed;
125 using css::table::CellAddress;
126 using css::sheet::XFormulaParser;
127 using ::oox::core::XmlFilterBase;
128 using ::sax_fastparser::FSHelperPtr;
129 
130 namespace cssc = css::chart;
131 
132 namespace oox::drawingml {
133 
134 namespace {
135 
isPrimaryAxes(sal_Int32 nIndex)136 bool isPrimaryAxes(sal_Int32 nIndex)
137 {
138     assert(nIndex == 0 || nIndex == 1);
139     return nIndex != 1;
140 }
141 
142 class lcl_MatchesRole
143 {
144 public:
lcl_MatchesRole(OUString aRole)145     explicit lcl_MatchesRole( OUString aRole ) :
146             m_aRole(std::move( aRole ))
147     {}
148 
operator ()(const Reference<chart2::data::XLabeledDataSequence> & xSeq) const149     bool operator () ( const Reference< chart2::data::XLabeledDataSequence > & xSeq ) const
150     {
151         if( !xSeq.is() )
152             return  false;
153         Reference< beans::XPropertySet > xProp( xSeq->getValues(), uno::UNO_QUERY );
154         OUString aRole;
155 
156         return ( xProp.is() &&
157                  (xProp->getPropertyValue( u"Role"_ustr ) >>= aRole ) &&
158                  m_aRole == aRole );
159     }
160 
161 private:
162     OUString m_aRole;
163 };
164 
outputStyleEntry(FSHelperPtr pFS,sal_Int32 nElTokenId)165 void outputStyleEntry(FSHelperPtr pFS, sal_Int32 nElTokenId)
166 {
167     // Just default values for now
168     pFS->startElement(FSNS(XML_cs, nElTokenId));
169     pFS->singleElement(FSNS(XML_cs, XML_lnRef), XML_idx, "0");
170     pFS->singleElement(FSNS(XML_cs, XML_fillRef), XML_idx, "0");
171     pFS->singleElement(FSNS(XML_cs, XML_effectRef), XML_idx, "0");
172     pFS->singleElement(FSNS(XML_cs, XML_fontRef), XML_idx, "minor");
173     pFS->endElement(FSNS(XML_cs, nElTokenId));
174 }
175 
outputChartAreaStyleEntry(FSHelperPtr pFS)176 void outputChartAreaStyleEntry(FSHelperPtr pFS)
177 {
178     // Just default values for now
179     pFS->startElement(FSNS(XML_cs, XML_chartArea), XML_mods, "allowNoFillOverride allowNoLineOverride");
180     pFS->singleElement(FSNS(XML_cs, XML_lnRef), XML_idx, "0");
181     pFS->singleElement(FSNS(XML_cs, XML_fillRef), XML_idx, "0");
182     pFS->singleElement(FSNS(XML_cs, XML_effectRef), XML_idx, "0");
183 
184     pFS->startElement(FSNS(XML_cs, XML_fontRef), XML_idx, "minor");
185     pFS->singleElement(FSNS(XML_a, XML_schemeClr), XML_val, "tx1");
186     pFS->endElement(FSNS(XML_cs, XML_fontRef));
187 
188     pFS->startElement(FSNS(XML_cs, XML_spPr));
189 
190     pFS->startElement(FSNS(XML_a, XML_solidFill));
191     pFS->singleElement(FSNS(XML_a, XML_schemeClr), XML_val, "bg1");
192     pFS->endElement(FSNS(XML_a, XML_solidFill));
193 
194     pFS->startElement(FSNS(XML_a, XML_ln), XML_w, "9525", XML_cap, "flat",
195             XML_cmpd, "sng", XML_algn, "ctr");
196     pFS->startElement(FSNS(XML_a, XML_solidFill));
197     pFS->startElement(FSNS(XML_a, XML_schemeClr), XML_val, "tx1");
198     pFS->singleElement(FSNS(XML_a, XML_lumMod), XML_val, "15000");
199     pFS->singleElement(FSNS(XML_a, XML_lumOff), XML_val, "85000");
200     pFS->endElement(FSNS(XML_a, XML_schemeClr));
201     pFS->endElement(FSNS(XML_a, XML_solidFill));
202     pFS->singleElement(FSNS(XML_a, XML_round));
203     pFS->endElement(FSNS(XML_a, XML_ln));
204 
205     pFS->endElement(FSNS(XML_cs, XML_spPr));
206 
207     pFS->endElement(FSNS(XML_cs, XML_chartArea));
208 }
209 
outputDataPointStyleEntry(FSHelperPtr pFS)210 void outputDataPointStyleEntry(FSHelperPtr pFS)
211 {
212     pFS->startElement(FSNS(XML_cs, XML_dataPoint));
213     pFS->singleElement(FSNS(XML_cs, XML_lnRef), XML_idx, "0");
214 
215     pFS->startElement(FSNS(XML_cs, XML_fillRef), XML_idx, "0");
216     pFS->singleElement(FSNS(XML_cs, XML_styleClr), XML_val, "auto");
217     pFS->endElement(FSNS(XML_cs, XML_fillRef));
218 
219     pFS->singleElement(FSNS(XML_cs, XML_effectRef), XML_idx, "0");
220 
221     pFS->startElement(FSNS(XML_cs, XML_fontRef), XML_idx, "minor");
222     pFS->singleElement(FSNS(XML_cs, XML_schemeClr), XML_val, "tx1");
223     pFS->endElement(FSNS(XML_cs, XML_fontRef));
224 
225     pFS->startElement(FSNS(XML_cs, XML_spPr));
226     pFS->startElement(FSNS(XML_a, XML_solidFill));
227     pFS->singleElement(FSNS(XML_a, XML_schemeClr), XML_val, "phClr");
228     pFS->endElement(FSNS(XML_a, XML_solidFill));
229     pFS->endElement(FSNS(XML_cs, XML_spPr));
230 
231     pFS->endElement(FSNS(XML_cs, XML_dataPoint));
232 }
233 
splitDataSeriesByAxis(const Reference<chart2::XChartType> & xChartType)234 std::vector<Sequence<Reference<chart2::XDataSeries> > > splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType)
235 {
236     std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries;
237     std::map<sal_Int32, size_t> aMapAxisToIndex;
238 
239     Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY);
240     if (xDSCnt.is())
241     {
242         sal_Int32 nAxisIndexOfFirstSeries = -1;
243         const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
244         for (const uno::Reference<chart2::XDataSeries>& xSeries : aSeriesSeq)
245         {
246             Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
247             if (!xPropSet.is())
248                 continue;
249 
250             sal_Int32 nAxisIndex = -1;
251             uno::Any aAny = xPropSet->getPropertyValue(u"AttachedAxisIndex"_ustr);
252             aAny >>= nAxisIndex;
253             size_t nVectorPos = 0;
254             if (nAxisIndexOfFirstSeries == -1)
255             {
256                 nAxisIndexOfFirstSeries = nAxisIndex;
257             }
258 
259             auto it = aMapAxisToIndex.find(nAxisIndex);
260             if (it == aMapAxisToIndex.end())
261             {
262                 aSplitSeries.emplace_back();
263                 nVectorPos = aSplitSeries.size() - 1;
264                 aMapAxisToIndex.insert(std::pair<sal_Int32, size_t>(nAxisIndex, nVectorPos));
265             }
266             else
267             {
268                 nVectorPos = it->second;
269             }
270 
271             uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq = aSplitSeries[nVectorPos];
272             sal_Int32 nLength = rAxisSeriesSeq.getLength();
273             rAxisSeriesSeq.realloc(nLength + 1);
274             rAxisSeriesSeq.getArray()[nLength] = xSeries;
275         }
276         // if the first series attached to secondary axis, then export those series first, which are attached to primary axis
277         // also the MS Office export every time in this order
278         if (aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1)
279         {
280             std::swap(aSplitSeries[0], aSplitSeries[1]);
281         }
282     }
283 
284     return aSplitSeries;
285 }
286 
287 }   // unnamed namespace
288 
lcl_getCategories(const Reference<chart2::XDiagram> & xDiagram,bool * pbHasDateCategories)289 static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories(
290         const Reference< chart2::XDiagram > & xDiagram, bool *pbHasDateCategories )
291 {
292     *pbHasDateCategories = false;
293     Reference< chart2::data::XLabeledDataSequence >  xResult;
294     try
295     {
296         Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
297             xDiagram, uno::UNO_QUERY_THROW );
298         const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
299             xCooSysCnt->getCoordinateSystems());
300         for( const auto& xCooSys : aCooSysSeq )
301         {
302             OSL_ASSERT( xCooSys.is());
303             for( sal_Int32 nN = xCooSys->getDimension(); nN--; )
304             {
305                 const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nN);
306                 for(sal_Int32 nI=0; nI<=nMaxAxisIndex; ++nI)
307                 {
308                     Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension( nN, nI );
309                     OSL_ASSERT( xAxis.is());
310                     if( xAxis.is())
311                     {
312                         chart2::ScaleData aScaleData = xAxis->getScaleData();
313                         if( aScaleData.Categories.is())
314                         {
315                             *pbHasDateCategories = aScaleData.AxisType == chart2::AxisType::DATE;
316                             xResult.set( aScaleData.Categories );
317                             break;
318                         }
319                     }
320                 }
321             }
322         }
323     }
324     catch( const uno::Exception & )
325     {
326         DBG_UNHANDLED_EXCEPTION("oox");
327     }
328 
329     return xResult;
330 }
331 
332 static Reference< chart2::data::XLabeledDataSequence >
lcl_getDataSequenceByRole(const Sequence<Reference<chart2::data::XLabeledDataSequence>> & aLabeledSeq,const OUString & rRole)333     lcl_getDataSequenceByRole(
334         const Sequence< Reference< chart2::data::XLabeledDataSequence > > & aLabeledSeq,
335         const OUString & rRole )
336 {
337     Reference< chart2::data::XLabeledDataSequence > aNoResult;
338 
339     const Reference< chart2::data::XLabeledDataSequence > * pBegin = aLabeledSeq.getConstArray();
340     const Reference< chart2::data::XLabeledDataSequence > * pEnd = pBegin + aLabeledSeq.getLength();
341     const Reference< chart2::data::XLabeledDataSequence > * pMatch =
342         ::std::find_if( pBegin, pEnd, lcl_MatchesRole( rRole ));
343 
344     if( pMatch != pEnd )
345         return *pMatch;
346 
347     return aNoResult;
348 }
349 
lcl_hasCategoryLabels(const Reference<chart2::XChartDocument> & xChartDoc)350 static bool lcl_hasCategoryLabels( const Reference< chart2::XChartDocument >& xChartDoc )
351 {
352     //categories are always the first sequence
353     Reference< chart2::XDiagram > xDiagram( xChartDoc->getFirstDiagram());
354     bool bDateCategories;
355     Reference< chart2::data::XLabeledDataSequence > xCategories(
356             lcl_getCategories( xDiagram, &bDateCategories ) );
357     return xCategories.is();
358 }
359 
lcl_isCategoryAxisShifted(const Reference<chart2::XDiagram> & xDiagram)360 static bool lcl_isCategoryAxisShifted( const Reference< chart2::XDiagram >& xDiagram )
361 {
362     bool bCategoryPositionShifted = false;
363     try
364     {
365         Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
366             xDiagram, uno::UNO_QUERY_THROW);
367         const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
368             xCooSysCnt->getCoordinateSystems());
369         for (const auto& xCooSys : aCooSysSeq)
370         {
371             OSL_ASSERT(xCooSys.is());
372             if( 0 < xCooSys->getDimension() && 0 <= xCooSys->getMaximumAxisIndexByDimension(0) )
373             {
374                 Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, 0);
375                 OSL_ASSERT(xAxis.is());
376                 if (xAxis.is())
377                 {
378                     chart2::ScaleData aScaleData = xAxis->getScaleData();
379                     bCategoryPositionShifted = aScaleData.ShiftedCategoryPosition;
380                     break;
381                 }
382             }
383         }
384     }
385     catch (const uno::Exception&)
386     {
387         DBG_UNHANDLED_EXCEPTION("oox");
388     }
389 
390     return bCategoryPositionShifted;
391 }
392 
lcl_getCategoryAxisType(const Reference<chart2::XDiagram> & xDiagram,sal_Int32 nDimensionIndex,sal_Int32 nAxisIndex)393 static sal_Int32 lcl_getCategoryAxisType( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex )
394 {
395     sal_Int32 nAxisType = -1;
396     try
397     {
398         Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
399             xDiagram, uno::UNO_QUERY_THROW);
400         const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
401             xCooSysCnt->getCoordinateSystems());
402         for( const auto& xCooSys : aCooSysSeq )
403         {
404             OSL_ASSERT(xCooSys.is());
405             if( nDimensionIndex < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(nDimensionIndex) )
406             {
407                 Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(nDimensionIndex, nAxisIndex);
408                 OSL_ASSERT(xAxis.is());
409                 if( xAxis.is() )
410                 {
411                     chart2::ScaleData aScaleData = xAxis->getScaleData();
412                     nAxisType = aScaleData.AxisType;
413                     break;
414                 }
415             }
416         }
417     }
418     catch (const uno::Exception&)
419     {
420         DBG_UNHANDLED_EXCEPTION("oox");
421     }
422 
423     return nAxisType;
424 }
425 
lclGetTimeUnitToken(sal_Int32 nTimeUnit)426 static OUString lclGetTimeUnitToken( sal_Int32 nTimeUnit )
427 {
428     switch( nTimeUnit )
429     {
430         case cssc::TimeUnit::DAY:      return u"days"_ustr;
431         case cssc::TimeUnit::MONTH:    return u"months"_ustr;
432         case cssc::TimeUnit::YEAR:     return u"years"_ustr;
433         default:                       OSL_ENSURE(false, "lclGetTimeUnitToken - unexpected time unit");
434     }
435     return u"days"_ustr;
436 }
437 
lcl_getDateTimeIncrement(const Reference<chart2::XDiagram> & xDiagram,sal_Int32 nAxisIndex)438 static cssc::TimeIncrement lcl_getDateTimeIncrement( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nAxisIndex )
439 {
440     cssc::TimeIncrement aTimeIncrement;
441     try
442     {
443         Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
444             xDiagram, uno::UNO_QUERY_THROW);
445         const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
446             xCooSysCnt->getCoordinateSystems());
447         for( const auto& xCooSys : aCooSysSeq )
448         {
449             OSL_ASSERT(xCooSys.is());
450             if( 0 < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(0) )
451             {
452                 Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, nAxisIndex);
453                 OSL_ASSERT(xAxis.is());
454                 if( xAxis.is() )
455                 {
456                     chart2::ScaleData aScaleData = xAxis->getScaleData();
457                     aTimeIncrement = aScaleData.TimeIncrement;
458                     break;
459                 }
460             }
461         }
462     }
463     catch (const uno::Exception&)
464     {
465         DBG_UNHANDLED_EXCEPTION("oox");
466     }
467 
468     return aTimeIncrement;
469 }
470 
lcl_isSeriesAttachedToFirstAxis(const Reference<chart2::XDataSeries> & xDataSeries)471 static bool lcl_isSeriesAttachedToFirstAxis(
472     const Reference< chart2::XDataSeries > & xDataSeries )
473 {
474     bool bResult=true;
475 
476     try
477     {
478         sal_Int32 nAxisIndex = 0;
479         Reference< beans::XPropertySet > xProp( xDataSeries, uno::UNO_QUERY_THROW );
480         xProp->getPropertyValue(u"AttachedAxisIndex"_ustr) >>= nAxisIndex;
481         bResult = (0==nAxisIndex);
482     }
483     catch( const uno::Exception & )
484     {
485         DBG_UNHANDLED_EXCEPTION("oox");
486     }
487 
488     return bResult;
489 }
490 
lcl_flattenStringSequence(const Sequence<OUString> & rSequence)491 static OUString lcl_flattenStringSequence( const Sequence< OUString > & rSequence )
492 {
493     OUStringBuffer aResult;
494     bool bPrecedeWithSpace = false;
495     for( const auto& rString : rSequence )
496     {
497         if( !rString.isEmpty())
498         {
499             if( bPrecedeWithSpace )
500                 aResult.append( ' ' );
501             aResult.append( rString );
502             bPrecedeWithSpace = true;
503         }
504     }
505     return aResult.makeStringAndClear();
506 }
507 
lcl_writeChartexString(const FSHelperPtr & pFS,std::u16string_view sOut)508 static void lcl_writeChartexString(const FSHelperPtr& pFS, std::u16string_view sOut)
509 {
510     pFS->startElement(FSNS(XML_cx, XML_tx));
511     // cell range doesn't seem to be supported in chartex?
512     // TODO: also handle <cx:rich>
513     pFS->startElement(FSNS(XML_cx, XML_txData));
514     // TODO: also handle <cx:f> <cx:v>
515     pFS->startElement(FSNS(XML_cx, XML_v));
516     pFS->writeEscaped(sOut);
517     pFS->endElement( FSNS( XML_cx, XML_v ) );
518     pFS->endElement( FSNS( XML_cx, XML_txData ) );
519     pFS->endElement( FSNS( XML_cx, XML_tx ) );
520 }
521 
lcl_getLabelSequence(const Reference<chart2::data::XDataSequence> & xLabelSeq)522 static Sequence< OUString > lcl_getLabelSequence( const Reference< chart2::data::XDataSequence > & xLabelSeq )
523 {
524     Sequence< OUString > aLabels;
525 
526     uno::Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xLabelSeq, uno::UNO_QUERY );
527     if( xTextualDataSequence.is())
528     {
529         aLabels = xTextualDataSequence->getTextualData();
530     }
531     else if( xLabelSeq.is())
532     {
533         const Sequence< uno::Any > aAnies( xLabelSeq->getData());
534         aLabels.realloc( aAnies.getLength());
535         auto pLabels = aLabels.getArray();
536         for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
537             aAnies[i] >>= pLabels[i];
538     }
539 
540     return aLabels;
541 }
542 
lcl_fillCategoriesIntoStringVector(const Reference<chart2::data::XDataSequence> & xCategories,::std::vector<OUString> & rOutCategories)543 static void lcl_fillCategoriesIntoStringVector(
544     const Reference< chart2::data::XDataSequence > & xCategories,
545     ::std::vector< OUString > & rOutCategories )
546 {
547     OSL_ASSERT( xCategories.is());
548     if( !xCategories.is())
549         return;
550     Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xCategories, uno::UNO_QUERY );
551     if( xTextualDataSequence.is())
552     {
553         rOutCategories.clear();
554         const Sequence< OUString > aTextData( xTextualDataSequence->getTextualData());
555         rOutCategories.insert( rOutCategories.end(), aTextData.begin(), aTextData.end() );
556     }
557     else
558     {
559         Sequence< uno::Any > aAnies( xCategories->getData());
560         rOutCategories.resize( aAnies.getLength());
561         for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
562             aAnies[i] >>= rOutCategories[i];
563     }
564 }
565 
lcl_getAllValuesFromSequence(const Reference<chart2::data::XDataSequence> & xSeq)566 static ::std::vector< double > lcl_getAllValuesFromSequence( const Reference< chart2::data::XDataSequence > & xSeq )
567 {
568     ::std::vector< double > aResult;
569 
570     Reference< chart2::data::XNumericalDataSequence > xNumSeq( xSeq, uno::UNO_QUERY );
571     if( xNumSeq.is())
572     {
573         const Sequence< double > aValues( xNumSeq->getNumericalData());
574         aResult.insert( aResult.end(), aValues.begin(), aValues.end() );
575     }
576     else if( xSeq.is())
577     {
578         Sequence< uno::Any > aAnies( xSeq->getData());
579         aResult.resize( aAnies.getLength(), std::numeric_limits<double>::quiet_NaN() );
580         for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
581             aAnies[i] >>= aResult[i];
582     }
583     return aResult;
584 }
585 
586 namespace
587 {
588 
589 constexpr auto constChartTypeMap = frozen::make_unordered_map<std::u16string_view, chart::TypeId>(
590 {
591     { u"com.sun.star.chart.BarDiagram", chart::TYPEID_BAR },
592     { u"com.sun.star.chart2.ColumnChartType",  chart::TYPEID_BAR },
593     { u"com.sun.star.chart.AreaDiagram",  chart::TYPEID_AREA },
594     { u"com.sun.star.chart2.AreaChartType",  chart::TYPEID_AREA },
595     { u"com.sun.star.chart.LineDiagram",  chart::TYPEID_LINE },
596     { u"com.sun.star.chart2.LineChartType",  chart::TYPEID_LINE },
597     { u"com.sun.star.chart.PieDiagram",  chart::TYPEID_PIE },
598     { u"com.sun.star.chart2.PieChartType",  chart::TYPEID_PIE },
599     { u"com.sun.star.chart.DonutDiagram",  chart::TYPEID_DOUGHNUT },
600     { u"com.sun.star.chart2.DonutChartType",  chart::TYPEID_DOUGHNUT },
601     { u"com.sun.star.chart.XYDiagram",  chart::TYPEID_SCATTER },
602     { u"com.sun.star.chart2.ScatterChartType",  chart::TYPEID_SCATTER },
603     { u"com.sun.star.chart.NetDiagram",  chart::TYPEID_RADARLINE },
604     { u"com.sun.star.chart2.NetChartType",  chart::TYPEID_RADARLINE },
605     { u"com.sun.star.chart.FilledNetDiagram",  chart::TYPEID_RADARAREA },
606     { u"com.sun.star.chart2.FilledNetChartType",  chart::TYPEID_RADARAREA },
607     { u"com.sun.star.chart.StockDiagram",  chart::TYPEID_STOCK },
608     { u"com.sun.star.chart2.CandleStickChartType",  chart::TYPEID_STOCK },
609     { u"com.sun.star.chart.BubbleDiagram",  chart::TYPEID_BUBBLE },
610     { u"com.sun.star.chart2.BubbleChartType",  chart::TYPEID_BUBBLE },
611     { u"com.sun.star.chart.FunnelDiagram",  chart::TYPEID_FUNNEL },
612     { u"com.sun.star.chart2.FunnelChartType",  chart::TYPEID_FUNNEL },
613 });
614 
615 } // end anonymous namespace
616 
lcl_getChartType(std::u16string_view sChartType)617 static sal_Int32 lcl_getChartType(std::u16string_view sChartType)
618 {
619     auto aIterator = constChartTypeMap.find(sChartType);
620     if (aIterator == constChartTypeMap.end())
621         return chart::TYPEID_UNKNOWN;
622     return aIterator->second;
623 }
624 
lcl_generateRandomValue()625 static sal_Int32 lcl_generateRandomValue()
626 {
627     return comphelper::rng::uniform_int_distribution(0, 100000000-1);
628 }
629 
empty() const630 bool DataLabelsRange::empty() const
631 {
632     return maLabels.empty();
633 }
634 
count() const635 size_t DataLabelsRange::count() const
636 {
637     return maLabels.size();
638 }
639 
hasLabel(sal_Int32 nIndex) const640 bool DataLabelsRange::hasLabel(sal_Int32 nIndex) const
641 {
642     return maLabels.find(nIndex) != maLabels.end();
643 }
644 
getRange() const645 const OUString & DataLabelsRange::getRange() const
646 {
647     return maRange;
648 }
649 
setRange(const OUString & rRange)650 void DataLabelsRange::setRange(const OUString& rRange)
651 {
652     maRange = rRange;
653 }
654 
setLabel(sal_Int32 nIndex,const OUString & rText)655 void DataLabelsRange::setLabel(sal_Int32 nIndex, const OUString& rText)
656 {
657     maLabels.emplace(nIndex, rText);
658 }
659 
begin() const660 DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::begin() const
661 {
662     return maLabels.begin();
663 }
664 
end() const665 DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::end() const
666 {
667     return maLabels.end();
668 }
669 
ChartExport(sal_Int32 nXmlNamespace,FSHelperPtr pFS,Reference<frame::XModel> const & xModel,XmlFilterBase * pFB,DocumentType eDocumentType)670 ChartExport::ChartExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, Reference< frame::XModel > const & xModel, XmlFilterBase* pFB, DocumentType eDocumentType )
671     : DrawingML( std::move(pFS), pFB, eDocumentType )
672     , mnXmlNamespace( nXmlNamespace )
673     , mnSeriesCount(0)
674     , mxChartModel( xModel )
675     , mpURLTransformer(std::make_shared<URLTransformer>())
676     , mbHasCategoryLabels( false )
677     , mbHasZAxis( false )
678     , mbIs3DChart( false )
679     , mbStacked(false)
680     , mbPercent(false)
681     , mbHasDateCategories(false)
682 {
683 }
684 
SetURLTranslator(const std::shared_ptr<URLTransformer> & pTransformer)685 void ChartExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer)
686 {
687     mpURLTransformer = pTransformer;
688 }
689 
getChartType()690 sal_Int32 ChartExport::getChartType( )
691 {
692     OUString sChartType = mxDiagram->getDiagramType();
693     return lcl_getChartType( sChartType );
694 }
695 
696 namespace {
697 
createArguments(const OUString & rRangeRepresentation,bool bUseColumns)698 uno::Sequence< beans::PropertyValue > createArguments(
699     const OUString & rRangeRepresentation, bool bUseColumns)
700 {
701     css::chart::ChartDataRowSource eRowSource = css::chart::ChartDataRowSource_ROWS;
702     if (bUseColumns)
703         eRowSource = css::chart::ChartDataRowSource_COLUMNS;
704 
705     uno::Sequence<beans::PropertyValue> aArguments{
706         { u"DataRowSource"_ustr, -1, uno::Any(eRowSource), beans::PropertyState_DIRECT_VALUE },
707         { u"FirstCellAsLabel"_ustr, -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE },
708         { u"HasCategories"_ustr, -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE },
709         { u"CellRangeRepresentation"_ustr, -1, uno::Any(rRangeRepresentation),
710           beans::PropertyState_DIRECT_VALUE }
711     };
712 
713     return aArguments;
714 }
715 
getPrimaryDataSeries(const Reference<chart2::XChartType> & xChartType)716 Reference<chart2::XDataSeries> getPrimaryDataSeries(const Reference<chart2::XChartType>& xChartType)
717 {
718     Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY_THROW);
719 
720     // export dataseries for current chart-type
721     const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
722     for (const auto& rSeries : aSeriesSeq)
723     {
724         Reference<chart2::XDataSeries> xSource(rSeries, uno::UNO_QUERY);
725         if (xSource.is())
726             return xSource;
727     }
728 
729     return Reference<chart2::XDataSeries>();
730 }
731 
732 }
733 
getSplitCategoriesList(const OUString & rRange)734 Sequence< Sequence< OUString > > ChartExport::getSplitCategoriesList( const OUString& rRange )
735 {
736     Reference< chart2::XChartDocument > xChartDoc(getModel(), uno::UNO_QUERY);
737     OSL_ASSERT(xChartDoc.is());
738     if (xChartDoc.is())
739     {
740         Reference< chart2::data::XDataProvider > xDataProvider(xChartDoc->getDataProvider());
741         OSL_ENSURE(xDataProvider.is(), "No DataProvider");
742         if (xDataProvider.is())
743         {
744             //detect whether the first series is a row or a column
745             bool bSeriesUsesColumns = true;
746             Reference< chart2::XDiagram > xDiagram(xChartDoc->getFirstDiagram());
747             try
748             {
749                 Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(xDiagram, uno::UNO_QUERY_THROW);
750                 const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(xCooSysCnt->getCoordinateSystems());
751                 for (const auto& rCooSys : aCooSysSeq)
752                 {
753                     const Reference< chart2::XChartTypeContainer > xCTCnt(rCooSys, uno::UNO_QUERY_THROW);
754                     const Sequence< Reference< chart2::XChartType > > aChartTypeSeq(xCTCnt->getChartTypes());
755                     for (const auto& rChartType : aChartTypeSeq)
756                     {
757                         Reference< chart2::XDataSeries > xDataSeries = getPrimaryDataSeries(rChartType);
758                         if (xDataSeries.is())
759                         {
760                             uno::Reference< chart2::data::XDataSource > xSeriesSource(xDataSeries, uno::UNO_QUERY);
761                             const uno::Sequence< beans::PropertyValue > rArguments = xDataProvider->detectArguments(xSeriesSource);
762                             for (const beans::PropertyValue& rProperty : rArguments)
763                             {
764                                 if (rProperty.Name == "DataRowSource")
765                                 {
766                                     css::chart::ChartDataRowSource eRowSource;
767                                     if (rProperty.Value >>= eRowSource)
768                                     {
769                                         bSeriesUsesColumns = (eRowSource == css::chart::ChartDataRowSource_COLUMNS);
770                                         break;
771                                     }
772                                 }
773                             }
774                         }
775                     }
776                 }
777             }
778             catch (const uno::Exception &)
779             {
780                 DBG_UNHANDLED_EXCEPTION("chart2");
781             }
782             // detect we have an inner data table or not
783             if (xChartDoc->hasInternalDataProvider() && rRange == "categories")
784             {
785                 try
786                 {
787                     css::uno::Reference< css::chart2::XAnyDescriptionAccess > xDataAccess(xChartDoc->getDataProvider(), uno::UNO_QUERY);
788                     const Sequence< Sequence< uno::Any > >aAnyCategories(bSeriesUsesColumns ? xDataAccess->getAnyRowDescriptions() : xDataAccess->getAnyColumnDescriptions());
789                     auto pMax = std::max_element(aAnyCategories.begin(), aAnyCategories.end(),
790                         [](const Sequence<uno::Any>& a, const Sequence<uno::Any>& b) {
791                             return a.getLength() < b.getLength(); });
792 
793                     //minimum is 1!
794                     if (pMax != aAnyCategories.end() && pMax->getLength() > 1)
795                     {
796                         sal_Int32 nLevelCount = pMax->getLength();
797                         //we have complex categories
798                         //sort the categories name
799                         Sequence<Sequence<OUString>>aFinalSplitSource(nLevelCount);
800                         auto pFinalSplitSource = aFinalSplitSource.getArray();
801                         for (sal_Int32 i = 0; i < nLevelCount; i++)
802                         {
803                             sal_Int32 nElemLabel = 0;
804                             pFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength());
805                             auto pSeq = pFinalSplitSource[nLevelCount - i - 1].getArray();
806                             for (auto const& elemLabel : aAnyCategories)
807                             {
808                                 // make sure elemLabel[i] exists!
809                                 if (elemLabel.getLength() > i)
810                                 {
811                                     pSeq[nElemLabel] = elemLabel[i].get<OUString>();
812                                     nElemLabel++;
813                                 }
814                             }
815                         }
816                         return aFinalSplitSource;
817                     }
818                 }
819                 catch (const uno::Exception &)
820                 {
821                     DBG_UNHANDLED_EXCEPTION("oox");
822                 }
823             }
824             else
825             {
826                 try
827                 {
828                     uno::Reference< chart2::data::XDataSource > xCategoriesSource(xDataProvider->createDataSource(
829                         createArguments(rRange, bSeriesUsesColumns)));
830 
831                     if (xCategoriesSource.is())
832                     {
833                         const Sequence< Reference< chart2::data::XLabeledDataSequence >> aCategories = xCategoriesSource->getDataSequences();
834                         if (aCategories.getLength() > 1)
835                         {
836                             //we have complex categories
837                             //sort the categories name
838                             Sequence<Sequence<OUString>> aFinalSplitSource(aCategories.getLength());
839                             std::transform(aCategories.begin(), aCategories.end(),
840                                 std::reverse_iterator(asNonConstRange(aFinalSplitSource).end()),
841                                 [](const Reference<chart2::data::XLabeledDataSequence>& xCat) {
842                                     return lcl_getLabelSequence(xCat->getValues()); });
843                             return aFinalSplitSource;
844                         }
845                     }
846                 }
847                 catch (const uno::Exception &)
848                 {
849                     DBG_UNHANDLED_EXCEPTION("oox");
850                 }
851             }
852         }
853     }
854 
855     return Sequence< Sequence< OUString>>(0);
856 }
857 
parseFormula(const OUString & rRange)858 OUString ChartExport::parseFormula( const OUString& rRange )
859 {
860     OUString aResult;
861     Reference< XFormulaParser > xParser;
862     uno::Reference< lang::XMultiServiceFactory > xSF = GetFB()->getModelFactory();
863     if( xSF.is() )
864     {
865         try
866         {
867             xParser.set( xSF->createInstance(u"com.sun.star.sheet.FormulaParser"_ustr), UNO_QUERY );
868         }
869         catch( Exception& )
870         {
871         }
872     }
873 
874     SAL_WARN_IF(!xParser.is(), "oox", "creating formula parser failed");
875 
876     if( xParser.is() )
877     {
878         Reference< XPropertySet > xParserProps( xParser, uno::UNO_QUERY );
879         // rRange is the result of a
880         // css::chart2::data::XDataSequence::getSourceRangeRepresentation()
881         // call that returns the range in the document's current UI notation.
882         // Creating a FormulaParser defaults to the same notation, for
883         // parseFormula() do not attempt to override the FormulaConvention
884         // property with css::sheet::AddressConvention::OOO or some such.
885         /* TODO: it would be much better to introduce a
886          * getSourceRangeRepresentation(css::sheet::AddressConvention) to
887          * return the ranges in a specific convention than converting them with
888          * the overhead of creating an XFormulaParser for each... */
889         uno::Sequence<sheet::FormulaToken> aTokens = xParser->parseFormula( rRange, CellAddress( 0, 0, 0 ) );
890         if( xParserProps.is() )
891         {
892             xParserProps->setPropertyValue(u"FormulaConvention"_ustr, uno::Any(css::sheet::AddressConvention::XL_OOX) );
893             // For referencing named ranges correctly with special excel chart syntax.
894             xParserProps->setPropertyValue(u"RefConventionChartOOXML"_ustr, uno::Any(true) );
895         }
896         aResult = xParser->printFormula( aTokens, CellAddress( 0, 0, 0 ) );
897     }
898     else
899     {
900         //FIXME: currently just using simple converter, e.g $Sheet1.$A$1:$C$1 -> Sheet1!$A$1:$C$1
901         OUString aRange( rRange );
902         if( aRange.startsWith("$") )
903             aRange = aRange.copy(1);
904         aRange = aRange.replaceAll(".$", "!$" );
905         aResult = aRange;
906     }
907 
908     return aResult;
909 }
910 
WriteChartObj(const Reference<XShape> & xShape,sal_Int32 nID,sal_Int32 nChartCount)911 void ChartExport::WriteChartObj( const Reference< XShape >& xShape, sal_Int32 nID, sal_Int32 nChartCount )
912 {
913     FSHelperPtr pFS = GetFS();
914 
915     Reference< chart2::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
916     OSL_ASSERT( xChartDoc.is() );
917     if( !xChartDoc.is() )
918         return;
919 
920     // We need to get the new diagram here so we can know if this is a chartex
921     // chart.
922     mxNewDiagram.set( xChartDoc->getFirstDiagram());
923 
924     const bool bIsChartex = isChartexNotChartNS();
925 
926     if (bIsChartex) {
927         // Do the AlternateContent header
928         mpFS->startElementNS(XML_mc, XML_AlternateContent, FSNS(XML_xmlns, XML_mc),
929                 "http://schemas.openxmlformats.org/markup-compatibility/2006");
930         mpFS->startElementNS(XML_mc, XML_Choice,
931                 FSNS(XML_xmlns, XML_cx2), "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex",
932                 XML_Requires, "cx2");
933     }
934 
935     Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
936 
937     pFS->startElementNS(mnXmlNamespace, XML_graphicFrame);
938 
939     pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);
940 
941     // TODO: get the correct chart name chart id
942     OUString sName = u"Object 1"_ustr;
943     Reference< XNamed > xNamed( xShape, UNO_QUERY );
944     if (xNamed.is())
945         sName = xNamed->getName();
946 
947     pFS->startElementNS( mnXmlNamespace, XML_cNvPr,
948                           XML_id,     OString::number(nID),
949                           XML_name,   sName);
950 
951     OUString sURL;
952     if ( GetProperty( xShapeProps, u"URL"_ustr ) )
953         mAny >>= sURL;
954     if( !sURL.isEmpty() )
955     {
956         OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
957                 oox::getRelationship(Relationship::HYPERLINK),
958                 mpURLTransformer->getTransformedString(sURL),
959                 mpURLTransformer->isExternalURL(sURL));
960 
961         mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
962     }
963 
964     if (bIsChartex) {
965         pFS->startElement(FSNS(XML_a, XML_extLst));
966         pFS->startElement(FSNS(XML_a, XML_ext), XML_uri,
967             "{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}");
968         pFS->singleElement(FSNS(XML_a16, XML_creationId),
969                 FSNS(XML_xmlns, XML_a16), "http://schemas.microsoft.com/office/drawing/2014/main",
970                 XML_id, "{393D7C90-AF84-3958-641C-0FEC03FE8894}");
971 
972         pFS->endElement(FSNS(XML_a, XML_ext));
973         pFS->endElement(FSNS(XML_a, XML_extLst));
974     }
975 
976     pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
977 
978     pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);
979 
980     if( GetDocumentType() == DOCUMENT_PPTX )
981         pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
982     pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );
983 
984     // visual chart properties
985     WriteShapeTransformation( xShape, mnXmlNamespace );
986 
987     const char *sSchemaURL = bIsChartex?
988         "http://schemas.microsoft.com/office/drawing/2014/chartex" :
989         "http://schemas.openxmlformats.org/drawingml/2006/chart";
990 
991     // writer chart object
992     pFS->startElement(FSNS(XML_a, XML_graphic));
993     pFS->startElement( FSNS( XML_a, XML_graphicData ), XML_uri, sSchemaURL );
994     OUString sId;
995     const char* sFullPath = nullptr;
996     const char* sRelativePath = nullptr;
997     const char *sChartFnamePrefix = bIsChartex? "chartEx" : "chart";
998     switch( GetDocumentType() )
999     {
1000         case DOCUMENT_DOCX:
1001         {
1002             sFullPath = "word/charts/";
1003             sRelativePath = "charts/";
1004             break;
1005         }
1006         case DOCUMENT_PPTX:
1007         {
1008             sFullPath = "ppt/charts/";
1009             sRelativePath = "../charts/";
1010             break;
1011         }
1012         case DOCUMENT_XLSX:
1013         {
1014             sFullPath = "xl/charts/";
1015             sRelativePath = "../charts/";
1016             break;
1017         }
1018         default:
1019         {
1020             sFullPath = "charts/";
1021             sRelativePath = "charts/";
1022             break;
1023         }
1024     }
1025     OUString sFullStream = OUStringBuffer()
1026                             .appendAscii(sFullPath)
1027                             .appendAscii(sChartFnamePrefix)
1028                             .append(OUString::number(nChartCount) + ".xml")
1029                             .makeStringAndClear();
1030     OUString sRelativeStream = OUStringBuffer()
1031                             .appendAscii(sRelativePath)
1032                             .appendAscii(sChartFnamePrefix)
1033                             .append(OUString::number(nChartCount) + ".xml" )
1034                             .makeStringAndClear();
1035 
1036     const OUString sAppURL = bIsChartex?
1037         u"application/vnd.ms-office.chartex+xml"_ustr :
1038         u"application/vnd.openxmlformats-officedocument.drawingml.chart+xml"_ustr;
1039 
1040     const Relationship eChartRel = bIsChartex ?
1041         Relationship::CHARTEX :
1042         Relationship::CHART;
1043 
1044     FSHelperPtr pChart = CreateOutputStream(
1045             sFullStream,
1046             sRelativeStream,
1047             pFS->getOutputStream(),
1048             sAppURL,
1049             oox::getRelationship(eChartRel),
1050             &sId );
1051 
1052     XmlFilterBase* pFB = GetFB();
1053 
1054     if (bIsChartex) {
1055         // Use chartex namespace
1056         pFS->singleElement(  FSNS( XML_cx, XML_chart ),
1057                 FSNS(XML_xmlns, XML_cx), pFB->getNamespaceURL(OOX_NS(cx)),
1058                 FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)),
1059                 FSNS(XML_r, XML_id), sId );
1060     } else {
1061         pFS->singleElement(  FSNS( XML_c, XML_chart ),
1062                 FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
1063                 FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)),
1064                 FSNS(XML_r, XML_id), sId );
1065     }
1066 
1067     pFS->endElement( FSNS( XML_a, XML_graphicData ) );
1068     pFS->endElement( FSNS( XML_a, XML_graphic ) );
1069     pFS->endElementNS( mnXmlNamespace, XML_graphicFrame );
1070 
1071     if (bIsChartex) {
1072         // Do the AlternateContent fallback path
1073         pFS->endElementNS(XML_mc, XML_Choice);
1074         pFS->startElementNS(XML_mc, XML_Fallback);
1075         pFS->startElementNS(XML_xdr, XML_sp, XML_macro, "", XML_textlink, "");
1076         pFS->startElementNS(XML_xdr, XML_nvSpPr);
1077         pFS->singleElementNS(XML_xdr, XML_cNvPr, XML_id, "0", XML_name, "");
1078         pFS->startElementNS(XML_xdr, XML_cNvSpPr);
1079         pFS->singleElementNS(XML_a, XML_spLocks, XML_noTextEdit, "1");
1080         pFS->endElementNS(XML_xdr, XML_cNvSpPr);
1081         pFS->endElementNS(XML_xdr, XML_nvSpPr);
1082         pFS->startElementNS(XML_xdr, XML_spPr);
1083         pFS->startElementNS(XML_a, XML_xfrm);
1084         pFS->singleElementNS(XML_a, XML_off, XML_x, "6600825", XML_y, "2533650");
1085         pFS->singleElementNS(XML_a, XML_ext, XML_cx, "4572000", XML_cy, "2743200");
1086         pFS->endElementNS(XML_a, XML_xfrm);
1087         pFS->startElementNS(XML_a, XML_prstGeom, XML_prst, "rect");
1088         pFS->singleElementNS(XML_a, XML_avLst);
1089         pFS->endElementNS(XML_a, XML_prstGeom);
1090         pFS->startElementNS(XML_a, XML_solidFill);
1091         pFS->singleElementNS(XML_a, XML_prstClr, XML_val, "white");
1092         pFS->endElementNS(XML_a, XML_solidFill);
1093         pFS->startElementNS(XML_a, XML_ln, XML_w, "1");
1094         pFS->startElementNS(XML_a, XML_solidFill);
1095         pFS->singleElementNS(XML_a, XML_prstClr, XML_val, "green");
1096         pFS->endElementNS(XML_a, XML_solidFill);
1097         pFS->endElementNS(XML_a, XML_ln);
1098         pFS->endElementNS(XML_xdr, XML_spPr);
1099         pFS->startElementNS(XML_xdr, XML_txBody);
1100         pFS->singleElementNS(XML_a, XML_bodyPr, XML_vertOverflow, "clip", XML_horzOverflow, "clip");
1101         pFS->singleElementNS(XML_a, XML_lstStyle);
1102         pFS->startElementNS(XML_a, XML_p);
1103         pFS->startElementNS(XML_a, XML_r);
1104         pFS->singleElementNS(XML_a, XML_rPr, XML_sz, "1100");
1105         pFS->startElementNS(XML_a, XML_t);
1106 
1107         const std::string_view sErrTxt("This chart isn't available in your version of Excel.\n\n"
1108             "Editing this shape or saving this workbook into a different file format will permanently break the chart.");
1109         pFS->writeEscaped( sErrTxt );
1110 
1111         pFS->endElementNS(XML_a, XML_t);
1112         pFS->endElementNS(XML_a, XML_r);
1113         pFS->endElementNS(XML_a, XML_p);
1114         pFS->endElementNS(XML_xdr, XML_txBody);
1115         pFS->endElementNS(XML_xdr, XML_sp);
1116 
1117         pFS->endElementNS(XML_mc, XML_Fallback);
1118         pFS->endElementNS(XML_mc, XML_AlternateContent);
1119     }
1120 
1121     SetFS( pChart );
1122     ExportContent();
1123 
1124     if (bIsChartex) {
1125         SetFS( pChart );
1126         sRelativePath ="";
1127 
1128         FSHelperPtr pChartFS = GetFS();
1129 
1130         // output style and colorstyle files
1131 
1132         // first style
1133         static constexpr char sStyleFnamePrefix[] = "style";
1134         OUStringBuffer sFullStreamBuf;
1135         sFullStreamBuf.appendAscii(sFullPath);
1136         sFullStreamBuf = sFullStreamBuf + sStyleFnamePrefix + OUString::number(nChartCount) + ".xml";
1137         sFullStream = sFullStreamBuf.makeStringAndClear();
1138         OUStringBuffer sRelativeStreamBuf;
1139         sRelativeStreamBuf.appendAscii(sRelativePath);
1140         sRelativeStreamBuf = sRelativeStreamBuf + sStyleFnamePrefix + OUString::number(nChartCount) + ".xml";
1141         sRelativeStream = sRelativeStreamBuf.makeStringAndClear();
1142 
1143         FSHelperPtr pStyle = CreateOutputStream(
1144                 sFullStream,
1145                 sRelativeStream,
1146                 pChartFS->getOutputStream(),
1147                 u"application/vnd.ms-office.chartstyle+xml"_ustr,
1148                 oox::getRelationship(Relationship::CHARTSTYLE),
1149                 &sId,
1150                 true /* for some reason this doesn't have a header line */);
1151 
1152         SetFS( pStyle );
1153         pFS = GetFS();
1154 
1155         pFS->startElement(FSNS(XML_cs, XML_chartStyle),
1156                 FSNS( XML_xmlns, XML_cs ), pFB->getNamespaceURL(OOX_NS(cs)),
1157                 FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
1158                 XML_id, "419" /* no idea what this number is supposed to be */);
1159 
1160         outputStyleEntry(pFS, XML_axisTitle);;
1161         outputStyleEntry(pFS, XML_categoryAxis);
1162         outputChartAreaStyleEntry(pFS);
1163         outputStyleEntry(pFS, XML_dataLabel);
1164         outputDataPointStyleEntry(pFS);
1165         outputStyleEntry(pFS, XML_dataPoint3D);
1166         outputStyleEntry(pFS, XML_dataPointLine);
1167         outputStyleEntry(pFS, XML_dataPointMarker);
1168         outputStyleEntry(pFS, XML_dataPointWireframe);
1169         outputStyleEntry(pFS, XML_dataTable);
1170         outputStyleEntry(pFS, XML_downBar);
1171         outputStyleEntry(pFS, XML_dropLine);
1172         outputStyleEntry(pFS, XML_errorBar);
1173         outputStyleEntry(pFS, XML_floor);
1174         outputStyleEntry(pFS, XML_gridlineMajor);
1175         outputStyleEntry(pFS, XML_gridlineMinor);
1176         outputStyleEntry(pFS, XML_hiLoLine);
1177         outputStyleEntry(pFS, XML_leaderLine);
1178         outputStyleEntry(pFS, XML_legend);
1179         outputStyleEntry(pFS, XML_plotArea);
1180         outputStyleEntry(pFS, XML_plotArea3D);
1181         outputStyleEntry(pFS, XML_seriesAxis);
1182         outputStyleEntry(pFS, XML_seriesLine);
1183         outputStyleEntry(pFS, XML_title);
1184         outputStyleEntry(pFS, XML_trendline);
1185         outputStyleEntry(pFS, XML_trendlineLabel);
1186         outputStyleEntry(pFS, XML_upBar);
1187         outputStyleEntry(pFS, XML_valueAxis);
1188         outputStyleEntry(pFS, XML_wall);
1189 
1190         pFS->endElement(FSNS(XML_cs, XML_chartStyle));
1191 
1192         pStyle->endDocument();
1193 
1194         // now colorstyle
1195         static constexpr char sColorFnamePrefix[] = "colors";
1196         sFullStreamBuf = OUStringBuffer();
1197         sFullStreamBuf.appendAscii(sFullPath);
1198         sFullStreamBuf = sFullStreamBuf + sColorFnamePrefix + OUString::number(nChartCount) + ".xml";
1199         sFullStream = sFullStreamBuf.makeStringAndClear();
1200         sRelativeStreamBuf = OUStringBuffer();
1201         sRelativeStreamBuf.appendAscii(sRelativePath);
1202         sRelativeStreamBuf = sRelativeStreamBuf + sColorFnamePrefix + OUString::number(nChartCount) + ".xml";
1203         sRelativeStream = sRelativeStreamBuf.makeStringAndClear();
1204 
1205         FSHelperPtr pColorStyle = CreateOutputStream(
1206                 sFullStream,
1207                 sRelativeStream,
1208                 pChartFS->getOutputStream(),
1209                 u"application/vnd.ms-office.chartcolorstyle+xml"_ustr,
1210                 oox::getRelationship(Relationship::CHARTCOLORSTYLE),
1211                 &sId,
1212                 true /* also no header line */);
1213 
1214         SetFS( pColorStyle );
1215         pFS = GetFS();
1216 
1217         pFS->startElement(FSNS(XML_cs, XML_colorStyle),
1218                 FSNS( XML_xmlns, XML_cs ), pFB->getNamespaceURL(OOX_NS(cs)),
1219                 FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
1220                 XML_meth, "cycle",
1221                 XML_id, "10" /* no idea what this number is supposed to be */);
1222 
1223         pFS->singleElement(FSNS(XML_a, XML_schemeClr),
1224                 XML_val, "accent1");
1225 
1226         pFS->endElement(FSNS(XML_cs, XML_colorStyle));
1227 
1228         pColorStyle->endDocument();
1229     }
1230 
1231     pChart->endDocument();
1232 }
1233 
InitRangeSegmentationProperties(const Reference<chart2::XChartDocument> & xChartDoc)1234 void ChartExport::InitRangeSegmentationProperties( const Reference< chart2::XChartDocument > & xChartDoc )
1235 {
1236     if( !xChartDoc.is())
1237         return;
1238 
1239     try
1240     {
1241         Reference< chart2::data::XDataProvider > xDataProvider( xChartDoc->getDataProvider() );
1242         OSL_ENSURE( xDataProvider.is(), "No DataProvider" );
1243         if( xDataProvider.is())
1244         {
1245             mbHasCategoryLabels = lcl_hasCategoryLabels( xChartDoc );
1246         }
1247     }
1248     catch( const uno::Exception & )
1249     {
1250         DBG_UNHANDLED_EXCEPTION("oox");
1251     }
1252 }
1253 
ExportContent()1254 void ChartExport::ExportContent()
1255 {
1256     Reference< chart2::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
1257     OSL_ASSERT( xChartDoc.is() );
1258     if( !xChartDoc.is() )
1259         return;
1260     InitRangeSegmentationProperties( xChartDoc );
1261 
1262     const bool bIsChartex = isChartexNotChartNS();
1263     ExportContent_( bIsChartex );
1264 }
1265 
ExportContent_(bool bIsChartex)1266 void ChartExport::ExportContent_( bool bIsChartex )
1267 {
1268     Reference< css::chart::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
1269     if( xChartDoc.is())
1270     {
1271         // determine if data comes from the outside
1272         bool bIncludeTable = true;
1273 
1274         Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
1275         if( xNewDoc.is())
1276         {
1277             // check if we have own data.  If so we must not export the complete
1278             // range string, as this is our only indicator for having own or
1279             // external data. @todo: fix this in the file format!
1280             Reference< lang::XServiceInfo > xDPServiceInfo( xNewDoc->getDataProvider(), uno::UNO_QUERY );
1281             if( ! (xDPServiceInfo.is() && xDPServiceInfo->getImplementationName() == "com.sun.star.comp.chart.InternalDataProvider" ))
1282             {
1283                 bIncludeTable = false;
1284             }
1285         }
1286         exportChartSpace( xChartDoc, bIncludeTable, bIsChartex );
1287     }
1288     else
1289     {
1290         OSL_FAIL( "Couldn't export chart due to wrong XModel" );
1291     }
1292 }
1293 
exportChartSpace(const Reference<css::chart::XChartDocument> & xChartDoc,bool bIncludeTable,bool bIsChartex)1294 void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument >& xChartDoc,
1295                                     bool bIncludeTable,
1296                                     bool bIsChartex)
1297 {
1298     FSHelperPtr pFS = GetFS();
1299     XmlFilterBase* pFB = GetFB();
1300 
1301     const sal_Int32 nChartNS = bIsChartex ? XML_cx : XML_c;
1302 
1303     if (bIsChartex) {
1304         pFS->startElement( FSNS( nChartNS, XML_chartSpace ),
1305                 FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
1306                 FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel)),
1307                 FSNS( XML_xmlns, XML_cx ), pFB->getNamespaceURL(OOX_NS(cx)));
1308     } else {
1309         pFS->startElement( FSNS( nChartNS, XML_chartSpace ),
1310                 FSNS( XML_xmlns, XML_c ), pFB->getNamespaceURL(OOX_NS(dmlChart)),
1311                 FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
1312                 FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel)));
1313     }
1314 
1315     if( !bIncludeTable )
1316     {
1317         // TODO:external data
1318     }
1319     else
1320     {
1321         Reference< XPropertySet > xPropSet(xChartDoc, UNO_QUERY);
1322         Any aNullDate = xPropSet->getPropertyValue("NullDate");
1323         util::DateTime aDate;
1324         if ((aNullDate >>= aDate) && (aDate.Year == 1904 && aDate.Month == 1 && aDate.Day == 1))
1325         {
1326             pFS->singleElement(FSNS(XML_c, XML_date1904), XML_val, "1");
1327         }
1328         else
1329         {
1330             pFS->singleElement(FSNS(XML_c, XML_date1904), XML_val, "0");
1331         }
1332     }
1333 
1334     // TODO: get the correct editing language
1335     if (bIsChartex) {
1336         // chartData
1337         pFS->startElement(FSNS(XML_cx, XML_chartData));
1338 
1339         exportExternalData(xChartDoc, true);
1340         exportData_chartex(xChartDoc);
1341 
1342         pFS->endElement(FSNS(XML_cx, XML_chartData));
1343     } else {
1344         pFS->singleElement(FSNS(XML_c, XML_lang), XML_val, "en-US");
1345 
1346         pFS->singleElement(FSNS(XML_c, XML_roundedCorners), XML_val, "0");
1347     }
1348 
1349     // style
1350     if (!bIsChartex) {
1351         mxDiagram.set( xChartDoc->getDiagram() );
1352         Reference< XPropertySet > xPropSet(mxDiagram, uno::UNO_QUERY);
1353         if (GetProperty(xPropSet, u"StyleIndex"_ustr)) {
1354             sal_Int32 nStyleIdx = -1;
1355             mAny >>= nStyleIdx;
1356             assert(nStyleIdx >= 0);
1357             pFS->singleElement(FSNS(XML_c, XML_style), XML_val,
1358                     OUString::number(nStyleIdx));
1359         }
1360     }
1361 
1362     //XML_chart
1363     exportChart(xChartDoc, bIsChartex);
1364 
1365     // TODO: printSettings
1366     // TODO: text properties
1367     Reference< XPropertySet > xPropSet = xChartDoc->getArea();
1368     if( xPropSet.is() )
1369         exportShapeProps( xPropSet, bIsChartex );
1370 
1371     // TODO for chartex
1372     if (!bIsChartex) {
1373         //XML_externalData
1374         exportExternalData(xChartDoc, false);
1375     }
1376 
1377     // export additional shapes in chart
1378     if (!bIsChartex) {
1379         exportAdditionalShapes(xChartDoc);
1380     }
1381 
1382     pFS->endElement( FSNS( nChartNS, XML_chartSpace ) );
1383 }
1384 
exportData_chartex(const Reference<css::chart::XChartDocument> & xChartDoc)1385 void ChartExport::exportData_chartex( [[maybe_unused]] const Reference< css::chart::XChartDocument >& xChartDoc)
1386 {
1387     Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, uno::UNO_QUERY );
1388     if( ! xBCooSysCnt.is()) return;
1389     const Sequence< Reference< chart2::XCoordinateSystem > >
1390         aCooSysSeq( xBCooSysCnt->getCoordinateSystems());
1391 
1392     if (!aCooSysSeq.hasElements()) return;
1393 
1394     for( const auto& rCS : aCooSysSeq ) {
1395         Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY );
1396         if( ! xCTCnt.is())
1397             continue;
1398         const Sequence< Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes());
1399 
1400         for( const auto& rCT : aCTSeq ) {
1401             Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, uno::UNO_QUERY );
1402             if( ! xDSCnt.is())
1403                 return;
1404             Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY );
1405             if( ! xChartType.is())
1406                 continue;
1407 
1408             OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel();
1409 
1410             const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
1411 
1412             for (const auto& splitDataSeries : aSplitDataSeries) {
1413                 sal_Int32 nSeriesIndex = 0;
1414                 for( const auto& rSeries : splitDataSeries )
1415                 {
1416                     // export series
1417                     Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY );
1418                     if( !xSource.is()) continue;
1419 
1420                     Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
1421                         xSource->getDataSequences());
1422 
1423                     // search for main sequence and create a series element
1424                     sal_Int32 nMainSequenceIndex = -1;
1425                     sal_Int32 nSeriesLength = 0;
1426                     Reference< chart2::data::XDataSequence > xValueSeq;
1427                     Reference< chart2::data::XDataSequence > xLabelSeq;
1428                     sal_Int32 nSeqIdx=0;
1429                     for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx )
1430                     {
1431                         Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() );
1432                         if( nMainSequenceIndex==-1 )
1433                         {
1434                             Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY );
1435                             OUString aRole;
1436                             if( xSeqProp.is())
1437                                 xSeqProp->getPropertyValue(u"Role"_ustr) >>= aRole;
1438                             // "main" sequence
1439                             if( aRole == aLabelRole )
1440                             {
1441                                 xValueSeq.set( xTempValueSeq );
1442                                 xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel());
1443                                 nMainSequenceIndex = nSeqIdx;
1444                             }
1445                         }
1446                         sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0));
1447                         if( nSeriesLength < nSequenceLength )
1448                             nSeriesLength = nSequenceLength;
1449                     }
1450                     FSHelperPtr pFS = GetFS();
1451 
1452                     // The data id needs to agree with the id in exportSeries(). See DATA_ID_COMMENT
1453                     pFS->startElement(FSNS(XML_cx, XML_data), XML_id, OUString::number(nSeriesIndex++));
1454 
1455                     // .xlsx chartex files seem to have this magical "_xlchart.v2.0" string,
1456                     // and no explicit data, while .docx and .pptx contain the literal data,
1457                     // as well as a ../embeddings file (which LO doesn't seem to produce).
1458                     // But there's probably a smarter way to determine which pathway to take
1459                     // than based on document type.
1460                     if (GetDocumentType() == DOCUMENT_XLSX) {
1461                         // Just hard-coding this for now
1462                         pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, "val");
1463                         pFS->startElement(FSNS(XML_cx, XML_f));
1464                         pFS->writeEscaped("_xlchart.v2.0");    // I have no idea what this
1465                                                                 // means or what it should be in
1466                                                                 // general
1467                         pFS->endElement(FSNS(XML_cx, XML_f));
1468                         pFS->endElement(FSNS(XML_cx, XML_numDim));
1469                     } else {    // PPTX, DOCX
1470                         OUString aCellRange = mxCategoriesValues.is() ? mxCategoriesValues->getSourceRangeRepresentation() : OUString();
1471 #undef OUTPUT_SPLIT_CATEGORIES  // do we need this or not? TODO
1472 #ifdef OUTPUT_SPLIT_CATEGORIES
1473                         const Sequence< Sequence< OUString >> aFinalSplitSource = getSplitCategoriesList(aCellRange);
1474 #endif
1475                         aCellRange = parseFormula( aCellRange );
1476 
1477 #ifdef OUTPUT_SPLIT_CATEGORIES
1478                         if (aFinalSplitSource.getLength() > 1) {
1479 
1480                             // export multi level category axis labels
1481                             pFS->startElement(FSNS(XML_cx, XML_strDim), XML_type, "cat");
1482 
1483                             pFS->startElement(FSNS(XML_cx, XML_f));
1484                             pFS->writeEscaped(aCellRange);
1485                             pFS->endElement(FSNS(XML_cx, XML_f));
1486 
1487                             for (const auto& rSeq : aFinalSplitSource) {
1488                                 pFS->startElement(FSNS(XML_cx, XML_lvl),
1489                                         XML_ptCount, OString::number(aFinalSplitSource[0].getLength()));
1490 
1491                                 for (sal_Int32 j = 0; j < rSeq.getLength(); j++) {
1492                                     if(!rSeq[j].isEmpty()) {
1493                                         pFS->startElement(FSNS(XML_cx, XML_pt), XML_idx, OString::number(j));
1494                                         pFS->writeEscaped(rSeq[j]);
1495                                         pFS->endElement(FSNS(XML_cx, XML_pt));
1496                                     }
1497                                 }
1498                                 pFS->endElement(FSNS(XML_cx, XML_lvl));
1499                             }
1500 
1501                             pFS->endElement(FSNS(XML_cx, XML_strDim));
1502                         }
1503                         else
1504 #endif
1505                         {
1506                             // export single category axis labels
1507                             // TODO: seems like this should consider mbHasCategoryLabels
1508                             bool bWriteDateCategories = mbHasDateCategories;
1509                             OUString aNumberFormatString;
1510                             if (bWriteDateCategories)
1511                             {
1512                                 Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
1513                                 if( xAxisXSupp.is())
1514                                 {
1515                                     Reference< XPropertySet > xAxisProp = xAxisXSupp->getXAxis();
1516                                     if (GetProperty(xAxisProp, u"NumberFormat"_ustr))
1517                                     {
1518                                         sal_Int32 nKey = 0;
1519                                         mAny >>= nKey;
1520                                         aNumberFormatString = getNumberFormatCode(nKey);
1521                                     }
1522                                 }
1523                                 if (aNumberFormatString.isEmpty()) bWriteDateCategories = false;
1524                             }
1525 
1526                             // === Output the categories
1527                             if (bWriteDateCategories)
1528                             {
1529                                 std::vector<double> aDateCategories = lcl_getAllValuesFromSequence(xValueSeq);
1530                                 const sal_Int32 ptCount = aDateCategories.size();
1531 
1532                                 pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, "x"); // is "x" right?
1533                                 // TODO: check this
1534 
1535                                 pFS->startElement(FSNS(XML_cx, XML_f));
1536                                 pFS->writeEscaped(aCellRange);
1537                                 pFS->endElement(FSNS(XML_cx, XML_f));
1538 
1539                                 pFS->startElement(FSNS(XML_cx, XML_lvl),
1540                                         XML_ptCount, OString::number(ptCount),
1541                                         XML_formatCode, aNumberFormatString);
1542 
1543                                 for (sal_Int32 i = 0; i < ptCount; i++) {
1544                                     if (!std::isnan(aDateCategories[i])) {
1545                                         pFS->startElement(FSNS(XML_cx, XML_pt), XML_idx, OString::number(i));
1546                                         pFS->write(OString::number(aDateCategories[i]));
1547                                         pFS->endElement(FSNS(XML_cx, XML_pt));
1548                                     }
1549                                 }
1550 
1551                                 pFS->endElement(FSNS(XML_cx, XML_lvl));
1552                                 pFS->endElement(FSNS(XML_cx, XML_numDim));
1553                             }
1554                             else
1555                             {
1556                                 std::vector<OUString> aCategories;
1557                                 lcl_fillCategoriesIntoStringVector(xValueSeq, aCategories);
1558                                 const sal_Int32 ptCount = aCategories.size();
1559 
1560                                 // TODO: shouldn't have "cat" hard-coded here:
1561                                 // other options are colorStr, entityId
1562                                 pFS->startElement(FSNS(XML_cx, XML_strDim), XML_type, "cat");
1563 
1564                                 pFS->startElement(FSNS(XML_cx, XML_f));
1565                                 pFS->writeEscaped(aCellRange);
1566                                 pFS->endElement(FSNS(XML_cx, XML_f));
1567 
1568                                 pFS->startElement(FSNS(XML_cx, XML_lvl), XML_ptCount, OString::number(ptCount));
1569 
1570                                 for (sal_Int32 i = 0; i < ptCount; i++) {
1571                                     pFS->startElement(FSNS(XML_cx, XML_pt), XML_idx, OString::number(i));
1572                                     pFS->writeEscaped(aCategories[i]);
1573                                     pFS->endElement(FSNS(XML_cx, XML_pt));
1574                                 }
1575 
1576                                 pFS->endElement(FSNS(XML_cx, XML_lvl));
1577                                 pFS->endElement(FSNS(XML_cx, XML_strDim));
1578                             }
1579 
1580                             // === Output the values
1581                             pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, "val");
1582 
1583                             aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
1584                             aCellRange = parseFormula( aCellRange );
1585                             // TODO: need to handle XML_multiLvlStrRef according to aCellRange
1586 
1587                             pFS->startElement(FSNS(XML_cx, XML_f));
1588                             pFS->writeEscaped( aCellRange );
1589                             pFS->endElement( FSNS( XML_cx, XML_f ) );
1590 
1591                             ::std::vector< double > aValues = lcl_getAllValuesFromSequence( xValueSeq );
1592                             sal_Int32 ptCount = aValues.size();
1593                             OUString sNumberFormatString(u"General"_ustr);
1594                             const sal_Int32 nKey = xValueSeq.is() ? xValueSeq->getNumberFormatKeyByIndex(-1) : 0;
1595                             if (nKey > 0) {
1596                                 sNumberFormatString = getNumberFormatCode(nKey);
1597                             }
1598                             pFS->startElement(FSNS(XML_cx, XML_lvl),
1599                                     XML_ptCount, OString::number(ptCount),
1600                                     XML_formatCode,  sNumberFormatString);
1601 
1602                             for( sal_Int32 i = 0; i < ptCount; i++ ) {
1603 
1604                                 pFS->startElement(FSNS(XML_cx, XML_pt), XML_idx, OString::number(i));
1605                                 pFS->write(std::isnan(aValues[i]) ?  0 : aValues[i]);
1606                                 pFS->endElement(FSNS(XML_cx, XML_pt));
1607                             }
1608 
1609                             pFS->endElement(FSNS(XML_cx, XML_lvl));
1610                             pFS->endElement(FSNS(XML_cx, XML_numDim));
1611                         }
1612                     }
1613                     pFS->endElement(FSNS(XML_cx, XML_data));
1614                 }
1615             }
1616         }
1617     }
1618 }
1619 
exportExternalData(const Reference<css::chart::XChartDocument> & xChartDoc,bool bIsChartex)1620 void ChartExport::exportExternalData( const Reference< css::chart::XChartDocument >& xChartDoc,
1621         bool bIsChartex)
1622 {
1623     if (bIsChartex) return; // TODO!!
1624     // Embedded external data is grab bagged for docx file hence adding export part of
1625     // external data for docx files only.
1626     if(GetDocumentType() != DOCUMENT_DOCX)
1627         return;
1628 
1629     OUString externalDataPath;
1630     Reference< beans::XPropertySet > xDocPropSet( xChartDoc->getDiagram(), uno::UNO_QUERY );
1631     if( xDocPropSet.is())
1632     {
1633         try
1634         {
1635             Any aAny( xDocPropSet->getPropertyValue( u"ExternalData"_ustr ));
1636             aAny >>= externalDataPath;
1637         }
1638         catch( beans::UnknownPropertyException & )
1639         {
1640             SAL_WARN("oox", "Required property not found in ChartDocument");
1641         }
1642     }
1643     if(externalDataPath.isEmpty())
1644         return;
1645 
1646     // Here adding external data entry to relationship.
1647     OUString relationPath = externalDataPath;
1648     // Converting absolute path to relative path.
1649     if( externalDataPath[ 0 ] != '.' && externalDataPath[ 1 ] != '.')
1650     {
1651         sal_Int32 nSepPos = externalDataPath.indexOf( '/', 0 );
1652         if( nSepPos > 0)
1653         {
1654             relationPath = relationPath.copy( nSepPos,  ::std::max< sal_Int32 >( externalDataPath.getLength(), 0 ) -  nSepPos );
1655             relationPath = ".." + relationPath;
1656         }
1657     }
1658     FSHelperPtr pFS = GetFS();
1659     OUString type = oox::getRelationship(Relationship::PACKAGE);
1660     if (relationPath.endsWith(".bin"))
1661         type = oox::getRelationship(Relationship::OLEOBJECT);
1662 
1663     OUString sRelId = GetFB()->addRelation(pFS->getOutputStream(),
1664                     type,
1665                     relationPath);
1666     pFS->singleElementNS(XML_c, XML_externalData, FSNS(XML_r, XML_id), sRelId);
1667 }
1668 
exportAdditionalShapes(const Reference<css::chart::XChartDocument> & xChartDoc)1669 void ChartExport::exportAdditionalShapes( const Reference< css::chart::XChartDocument >& xChartDoc )
1670 {
1671     // Not used in chartex
1672 
1673     Reference< beans::XPropertySet > xDocPropSet(xChartDoc, uno::UNO_QUERY);
1674     if (!xDocPropSet.is())
1675         return;
1676 
1677     css::uno::Reference< css::drawing::XShapes > mxAdditionalShapes;
1678     // get a sequence of non-chart shapes
1679     try
1680     {
1681         Any aShapesAny = xDocPropSet->getPropertyValue(u"AdditionalShapes"_ustr);
1682         if( (aShapesAny >>= mxAdditionalShapes) && mxAdditionalShapes.is() )
1683         {
1684             OUString sId;
1685             const char* sFullPath = nullptr;
1686             const char* sRelativePath = nullptr;
1687             sal_Int32 nDrawing = getNewDrawingUniqueId();
1688 
1689             switch (GetDocumentType())
1690             {
1691                 case DOCUMENT_DOCX:
1692                 {
1693                     sFullPath = "word/drawings/drawing";
1694                     sRelativePath = "../drawings/drawing";
1695                     break;
1696                 }
1697                 case DOCUMENT_PPTX:
1698                 {
1699                     sFullPath = "ppt/drawings/drawing";
1700                     sRelativePath = "../drawings/drawing";
1701                     break;
1702                 }
1703                 case DOCUMENT_XLSX:
1704                 {
1705                     sFullPath = "xl/drawings/drawing";
1706                     sRelativePath = "../drawings/drawing";
1707                     break;
1708                 }
1709                 default:
1710                 {
1711                     sFullPath = "drawings/drawing";
1712                     sRelativePath = "drawings/drawing";
1713                     break;
1714                 }
1715             }
1716             OUString sFullStream = OUStringBuffer()
1717                 .appendAscii(sFullPath)
1718                 .append(OUString::number(nDrawing) + ".xml")
1719                 .makeStringAndClear();
1720             OUString sRelativeStream = OUStringBuffer()
1721                 .appendAscii(sRelativePath)
1722                 .append(OUString::number(nDrawing) + ".xml")
1723                 .makeStringAndClear();
1724 
1725             sax_fastparser::FSHelperPtr pDrawing = CreateOutputStream(
1726                 sFullStream,
1727                 sRelativeStream,
1728                 GetFS()->getOutputStream(),
1729                 u"application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"_ustr,
1730                 oox::getRelationship(Relationship::CHARTUSERSHAPES),
1731                 &sId);
1732 
1733             GetFS()->singleElementNS(XML_c, XML_userShapes, FSNS(XML_r, XML_id), sId);
1734 
1735             XmlFilterBase* pFB = GetFB();
1736             pDrawing->startElement(FSNS(XML_c, XML_userShapes),
1737                 FSNS(XML_xmlns, XML_cdr), pFB->getNamespaceURL(OOX_NS(dmlChartDr)),
1738                 FSNS(XML_xmlns, XML_a), pFB->getNamespaceURL(OOX_NS(dml)),
1739                 FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
1740                 FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)));
1741 
1742             const sal_Int32 nShapeCount(mxAdditionalShapes->getCount());
1743             for (sal_Int32 nShapeId = 0; nShapeId < nShapeCount; nShapeId++)
1744             {
1745                 Reference< drawing::XShape > xShape;
1746                 mxAdditionalShapes->getByIndex(nShapeId) >>= xShape;
1747                 SAL_WARN_IF(!xShape.is(), "xmloff.chart", "Shape without an XShape?");
1748                 if (!xShape.is())
1749                     continue;
1750 
1751                 // TODO: absSizeAnchor: we import both (absSizeAnchor and relSizeAnchor), but there is no essential difference between them.
1752                 pDrawing->startElement(FSNS(XML_cdr, XML_relSizeAnchor));
1753                 uno::Reference< beans::XPropertySet > xShapeProperties(xShape, uno::UNO_QUERY);
1754                 if( xShapeProperties.is() )
1755                 {
1756                     Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
1757                     awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
1758                     WriteFromTo( xShape, aPageSize, pDrawing );
1759 
1760                     ShapeExport aExport(XML_cdr, pDrawing, nullptr, GetFB(), GetDocumentType(), nullptr, true);
1761                     aExport.WriteShape(xShape);
1762                 }
1763                 pDrawing->endElement(FSNS(XML_cdr, XML_relSizeAnchor));
1764             }
1765             pDrawing->endElement(FSNS(XML_c, XML_userShapes));
1766             pDrawing->endDocument();
1767         }
1768     }
1769     catch (const uno::Exception&)
1770     {
1771         TOOLS_INFO_EXCEPTION("xmloff.chart", "AdditionalShapes not found");
1772     }
1773 }
1774 
exportChart(const Reference<css::chart::XChartDocument> & xChartDoc,bool bIsChartex)1775 void ChartExport::exportChart( const Reference< css::chart::XChartDocument >& xChartDoc,
1776         bool bIsChartex)
1777 {
1778     Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
1779     mxDiagram.set( xChartDoc->getDiagram() );
1780     if( xNewDoc.is()) {
1781         mxNewDiagram.set( xNewDoc->getFirstDiagram());
1782     }
1783 
1784     // get Properties of ChartDocument
1785     bool bHasMainTitle = false;
1786     bool bHasLegend = false;
1787     Reference< beans::XPropertySet > xDocPropSet( xChartDoc, uno::UNO_QUERY );
1788     if( xDocPropSet.is())
1789     {
1790         try
1791         {
1792             Any aAny( xDocPropSet->getPropertyValue(u"HasMainTitle"_ustr));
1793             aAny >>= bHasMainTitle;
1794             aAny = xDocPropSet->getPropertyValue(u"HasLegend"_ustr);
1795             aAny >>= bHasLegend;
1796         }
1797         catch( beans::UnknownPropertyException & )
1798         {
1799             SAL_WARN("oox", "Required property not found in ChartDocument");
1800         }
1801     } // if( xDocPropSet.is())
1802 
1803     Sequence< uno::Reference< chart2::XFormattedString > > xFormattedSubTitle;
1804     Reference< beans::XPropertySet > xPropSubTitle( xChartDoc->getSubTitle(), UNO_QUERY );
1805     if( xPropSubTitle.is())
1806     {
1807         OUString aSubTitle;
1808         if ((xPropSubTitle->getPropertyValue(u"String"_ustr) >>= aSubTitle) && !aSubTitle.isEmpty())
1809             xPropSubTitle->getPropertyValue(u"FormattedStrings"_ustr) >>= xFormattedSubTitle;
1810     }
1811 
1812     // chart element
1813     FSHelperPtr pFS = GetFS();
1814 
1815     const sal_Int32 nChartNS = bIsChartex ? XML_cx : XML_c;
1816     pFS->startElement(FSNS(nChartNS, XML_chart));
1817 
1818     // titles
1819     if( bHasMainTitle )
1820     {
1821         exportTitle( xChartDoc->getTitle(), bIsChartex, xFormattedSubTitle);
1822         if (!bIsChartex) {
1823             pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
1824         }
1825     }
1826     else if( xFormattedSubTitle.hasElements() )
1827     {
1828         exportTitle( xChartDoc->getSubTitle(), bIsChartex );
1829         if (!bIsChartex) {
1830             pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
1831         }
1832     }
1833     else if (!bIsChartex) {
1834         pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "1");
1835     }
1836 
1837     InitPlotArea( );
1838     if( mbIs3DChart )
1839     {
1840         if (!bIsChartex) {
1841             exportView3D();
1842 
1843             // floor
1844             Reference< beans::XPropertySet > xFloor = mxNewDiagram->getFloor();
1845             if( xFloor.is() )
1846             {
1847                 pFS->startElement(FSNS(XML_c, XML_floor));
1848                 exportShapeProps( xFloor, false );
1849                 pFS->endElement( FSNS( XML_c, XML_floor ) );
1850             }
1851 
1852             // LibreOffice doesn't distinguish between sideWall and backWall (both are using the same color).
1853             // It is controlled by the same Wall property.
1854             Reference< beans::XPropertySet > xWall = mxNewDiagram->getWall();
1855             if( xWall.is() )
1856             {
1857                 // sideWall
1858                 pFS->startElement(FSNS(XML_c, XML_sideWall));
1859                 exportShapeProps( xWall, false );
1860                 pFS->endElement( FSNS( XML_c, XML_sideWall ) );
1861 
1862                 // backWall
1863                 pFS->startElement(FSNS(XML_c, XML_backWall));
1864                 exportShapeProps( xWall, false );
1865                 pFS->endElement( FSNS( XML_c, XML_backWall ) );
1866             }
1867         }
1868     }
1869     // plot area
1870     exportPlotArea( xChartDoc, bIsChartex );
1871     // legend
1872     if( bHasLegend ) {
1873         exportLegend( xChartDoc, bIsChartex );
1874     }
1875 
1876     if (!bIsChartex) {
1877         uno::Reference<beans::XPropertySet> xDiagramPropSet(xChartDoc->getDiagram(), uno::UNO_QUERY);
1878         uno::Any aPlotVisOnly = xDiagramPropSet->getPropertyValue(u"IncludeHiddenCells"_ustr);
1879         bool bIncludeHiddenCells = false;
1880         aPlotVisOnly >>= bIncludeHiddenCells;
1881         pFS->singleElement(FSNS(XML_c, XML_plotVisOnly), XML_val, ToPsz10(!bIncludeHiddenCells));
1882 
1883         exportMissingValueTreatment(Reference<beans::XPropertySet>(mxDiagram, uno::UNO_QUERY));
1884     }
1885 
1886     pFS->endElement( FSNS( nChartNS, XML_chart ) );
1887 }
1888 
exportMissingValueTreatment(const uno::Reference<beans::XPropertySet> & xPropSet)1889 void ChartExport::exportMissingValueTreatment(const uno::Reference<beans::XPropertySet>& xPropSet)
1890 {
1891     if (!xPropSet.is())
1892         return;
1893 
1894     sal_Int32 nVal = 0;
1895     uno::Any aAny = xPropSet->getPropertyValue(u"MissingValueTreatment"_ustr);
1896     if (!(aAny >>= nVal))
1897         return;
1898 
1899     const char* pVal = nullptr;
1900     switch (nVal)
1901     {
1902         case cssc::MissingValueTreatment::LEAVE_GAP:
1903             pVal = "gap";
1904         break;
1905         case cssc::MissingValueTreatment::USE_ZERO:
1906             pVal = "zero";
1907         break;
1908         case cssc::MissingValueTreatment::CONTINUE:
1909             pVal = "span";
1910         break;
1911         default:
1912             SAL_WARN("oox", "unknown MissingValueTreatment value");
1913         break;
1914     }
1915 
1916     FSHelperPtr pFS = GetFS();
1917     pFS->singleElement(FSNS(XML_c, XML_dispBlanksAs), XML_val, pVal);
1918 }
1919 
exportLegend(const Reference<css::chart::XChartDocument> & xChartDoc,bool bIsChartex)1920 void ChartExport::exportLegend( const Reference< css::chart::XChartDocument >& xChartDoc,
1921         bool bIsChartex)
1922 {
1923     FSHelperPtr pFS = GetFS();
1924 
1925     Reference< beans::XPropertySet > xProp( xChartDoc->getLegend(), uno::UNO_QUERY );
1926     if( xProp.is() )
1927     {
1928         if (!bIsChartex) {
1929             pFS->startElement(FSNS(XML_c, XML_legend));
1930         }
1931 
1932         // position
1933         css::chart::ChartLegendPosition aLegendPos = css::chart::ChartLegendPosition_NONE;
1934         try
1935         {
1936             Any aAny( xProp->getPropertyValue( u"Alignment"_ustr ));
1937             aAny >>= aLegendPos;
1938         }
1939         catch( beans::UnknownPropertyException & )
1940         {
1941             SAL_WARN("oox", "Property Align not found in ChartLegend");
1942         }
1943 
1944         const char* strPos = nullptr;
1945         switch( aLegendPos )
1946         {
1947             case css::chart::ChartLegendPosition_LEFT:
1948                 strPos = "l";
1949                 break;
1950             case css::chart::ChartLegendPosition_RIGHT:
1951                 strPos = "r";
1952                 break;
1953             case css::chart::ChartLegendPosition_TOP:
1954                 strPos = "t";
1955                 break;
1956             case css::chart::ChartLegendPosition_BOTTOM:
1957                 strPos = "b";
1958                 break;
1959             case css::chart::ChartLegendPosition_NONE:
1960             case css::chart::ChartLegendPosition::ChartLegendPosition_MAKE_FIXED_SIZE:
1961                 // nothing
1962                 break;
1963         }
1964 
1965         if (!bIsChartex) {
1966             if( strPos != nullptr )
1967             {
1968                 pFS->singleElement(FSNS(XML_c, XML_legendPos), XML_val, strPos);
1969             }
1970 
1971             // legendEntry
1972             Reference<chart2::XCoordinateSystemContainer> xCooSysContainer(mxNewDiagram, UNO_QUERY_THROW);
1973             const Sequence<Reference<chart2::XCoordinateSystem>> xCooSysSequence(xCooSysContainer->getCoordinateSystems());
1974 
1975             sal_Int32 nIndex = 0;
1976             bool bShowLegendEntry;
1977             for (const auto& rCooSys : xCooSysSequence)
1978             {
1979                 PropertySet aCooSysProp(rCooSys);
1980                 bool bSwapXAndY = aCooSysProp.getBoolProperty(PROP_SwapXAndYAxis);
1981 
1982                 Reference<chart2::XChartTypeContainer> xChartTypeContainer(rCooSys, UNO_QUERY_THROW);
1983                 const Sequence<Reference<chart2::XChartType>> xChartTypeSequence(xChartTypeContainer->getChartTypes());
1984                 if (!xChartTypeSequence.hasElements())
1985                     continue;
1986 
1987                 for (const auto& rCT : xChartTypeSequence)
1988                 {
1989                     Reference<chart2::XDataSeriesContainer> xDSCont(rCT, UNO_QUERY);
1990                     if (!xDSCont.is())
1991                         continue;
1992 
1993                     OUString aChartType(rCT->getChartType());
1994                     bool bIsPie = lcl_getChartType(aChartType) == chart::TYPEID_PIE;
1995                     if (bIsPie)
1996                     {
1997                         PropertySet xChartTypeProp(rCT);
1998                         bIsPie = !xChartTypeProp.getBoolProperty(PROP_UseRings);
1999                     }
2000                     const Sequence<Reference<chart2::XDataSeries>> aDataSeriesSeq = xDSCont->getDataSeries();
2001                     if (bSwapXAndY)
2002                         nIndex += aDataSeriesSeq.getLength() - 1;
2003                     for (const auto& rDataSeries : aDataSeriesSeq)
2004                     {
2005                         PropertySet aSeriesProp(rDataSeries);
2006                         bool bVaryColorsByPoint = aSeriesProp.getBoolProperty(PROP_VaryColorsByPoint);
2007                         if (bVaryColorsByPoint || bIsPie)
2008                         {
2009                             Sequence<sal_Int32> deletedLegendEntriesSeq;
2010                             aSeriesProp.getProperty(deletedLegendEntriesSeq, PROP_DeletedLegendEntries);
2011                             for (const auto& deletedLegendEntry : std::as_const(deletedLegendEntriesSeq))
2012                             {
2013                                 pFS->startElement(FSNS(XML_c, XML_legendEntry));
2014                                 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val,
2015                                                    OString::number(nIndex + deletedLegendEntry));
2016                                 pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1");
2017                                 pFS->endElement(FSNS(XML_c, XML_legendEntry));
2018                             }
2019                             Reference<chart2::data::XDataSource> xDSrc(rDataSeries, UNO_QUERY);
2020                             if (!xDSrc.is())
2021                                 continue;
2022 
2023                             const Sequence<Reference<chart2::data::XLabeledDataSequence>> aDataSeqs = xDSrc->getDataSequences();
2024                             for (const auto& rDataSeq : aDataSeqs)
2025                             {
2026                                 Reference<chart2::data::XDataSequence> xValues = rDataSeq->getValues();
2027                                 if (!xValues.is())
2028                                     continue;
2029 
2030                                 sal_Int32 nDataSeqSize = xValues->getData().getLength();
2031                                 nIndex += nDataSeqSize;
2032                             }
2033                         }
2034                         else
2035                         {
2036                             bShowLegendEntry = aSeriesProp.getBoolProperty(PROP_ShowLegendEntry);
2037                             if (!bShowLegendEntry)
2038                             {
2039                                 pFS->startElement(FSNS(XML_c, XML_legendEntry));
2040                                 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val,
2041                                                    OString::number(nIndex));
2042                                 pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1");
2043                                 pFS->endElement(FSNS(XML_c, XML_legendEntry));
2044                             }
2045                             bSwapXAndY ? nIndex-- : nIndex++;
2046                         }
2047                     }
2048                     if (bSwapXAndY)
2049                         nIndex += aDataSeriesSeq.getLength() + 1;
2050                 }
2051             }
2052 
2053             uno::Any aRelativePos = xProp->getPropertyValue(u"RelativePosition"_ustr);
2054             if (aRelativePos.hasValue())
2055             {
2056                 pFS->startElement(FSNS(XML_c, XML_layout));
2057                 pFS->startElement(FSNS(XML_c, XML_manualLayout));
2058 
2059                 pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
2060                 pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
2061                 chart2::RelativePosition aPos = aRelativePos.get<chart2::RelativePosition>();
2062 
2063                 const double x = aPos.Primary;
2064                 const double y = aPos.Secondary;
2065 
2066                 pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
2067                 pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
2068 
2069                 uno::Any aRelativeSize = xProp->getPropertyValue(u"RelativeSize"_ustr);
2070                 if (aRelativeSize.hasValue())
2071                 {
2072                     chart2::RelativeSize aSize = aRelativeSize.get<chart2::RelativeSize>();
2073 
2074                     const double w = aSize.Primary;
2075                     const double h = aSize.Secondary;
2076 
2077                     pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
2078 
2079                     pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
2080                 }
2081 
2082                 SAL_WARN_IF(aPos.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position");
2083 
2084                 pFS->endElement(FSNS(XML_c, XML_manualLayout));
2085                 pFS->endElement(FSNS(XML_c, XML_layout));
2086             }
2087         }
2088 
2089         const char *sOverlay = nullptr;
2090         if (strPos != nullptr)
2091         {
2092             uno::Any aOverlay = xProp->getPropertyValue(u"Overlay"_ustr);
2093             if(aOverlay.get<bool>())
2094                 sOverlay = "1";
2095             else
2096                 sOverlay = "0";
2097         }
2098 
2099         if (bIsChartex) {
2100             pFS->startElement(FSNS(XML_cx, XML_legend),
2101                     XML_pos, strPos ? strPos : "r",
2102                     XML_align, "ctr",   // is this supported?
2103                     XML_overlay, sOverlay ? sOverlay : "0");
2104         } else {
2105             pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, sOverlay);
2106         }
2107 
2108         // shape properties
2109         exportShapeProps( xProp, bIsChartex );
2110 
2111         // draw-chart:txPr text properties
2112         exportTextProps( xProp, bIsChartex );
2113 
2114         if (bIsChartex) {
2115             pFS->endElement( FSNS( XML_cx, XML_legend ) );
2116         } else {
2117             pFS->endElement( FSNS( XML_c, XML_legend ) );
2118         }
2119     }
2120 }
2121 
exportTitle(const Reference<XShape> & xShape,bool bIsChartex,const css::uno::Sequence<uno::Reference<css::chart2::XFormattedString>> & xFormattedSubTitle)2122 void ChartExport::exportTitle( const Reference< XShape >& xShape, bool bIsChartex,
2123     const css::uno::Sequence< uno::Reference< css::chart2::XFormattedString > >& xFormattedSubTitle )
2124 {
2125     Sequence< uno::Reference< chart2::XFormattedString > > xFormattedTitle;
2126     Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
2127     if( xPropSet.is())
2128     {
2129         OUString aTitle;
2130         if ((xPropSet->getPropertyValue(u"String"_ustr) >>= aTitle) && !aTitle.isEmpty())
2131             xPropSet->getPropertyValue(u"FormattedStrings"_ustr) >>= xFormattedTitle;
2132     }
2133 
2134     // tdf#101322: add subtitle to title
2135     if (xFormattedSubTitle.hasElements())
2136     {
2137         if (!xFormattedTitle.hasElements())
2138         {
2139             xFormattedTitle = xFormattedSubTitle;
2140         }
2141         else
2142         {
2143             sal_uInt32 nLength = xFormattedTitle.size();
2144             const OUString aLastString = xFormattedTitle.getArray()[nLength - 1]->getString();
2145             xFormattedTitle.getArray()[nLength - 1]->setString(aLastString + OUStringChar('\n'));
2146             for (const uno::Reference<chart2::XFormattedString>& rxFS : xFormattedSubTitle)
2147             {
2148                 if (!rxFS->getString().isEmpty())
2149                 {
2150                     xFormattedTitle.realloc(nLength + 1);
2151                     xFormattedTitle.getArray()[nLength++] = rxFS;
2152                 }
2153             }
2154         }
2155     }
2156 
2157     if (!xFormattedTitle.hasElements())
2158         return;
2159 
2160     FSHelperPtr pFS = GetFS();
2161 
2162     if (bIsChartex) {
2163         pFS->startElement(FSNS(XML_cx, XML_title));
2164         lcl_writeChartexString(pFS, xFormattedTitle[0]->getString());
2165     } else {
2166         pFS->startElement(FSNS(XML_c, XML_title));
2167         pFS->startElement(FSNS(XML_c, XML_tx));
2168         pFS->startElement(FSNS(XML_c, XML_rich));
2169     }
2170 
2171     if (bIsChartex) {
2172         // shape properties
2173         if( xPropSet.is() )
2174         {
2175             exportShapeProps( xPropSet, bIsChartex );
2176         }
2177 
2178         pFS->startElement(FSNS(XML_cx, XML_txPr));
2179     }
2180 
2181     // TODO: bodyPr
2182     const char* sWritingMode = nullptr;
2183     bool bVertical = false;
2184     xPropSet->getPropertyValue(u"StackedText"_ustr) >>= bVertical;
2185     if( bVertical )
2186         sWritingMode = "wordArtVert";
2187 
2188     sal_Int32 nRotation = 0;
2189     xPropSet->getPropertyValue(u"TextRotation"_ustr) >>= nRotation;
2190 
2191     pFS->singleElement( FSNS( XML_a, XML_bodyPr ),
2192             XML_vert, sWritingMode,
2193             XML_rot, oox::drawingml::calcRotationValue(nRotation) );
2194     // TODO: lstStyle
2195     pFS->singleElement(FSNS(XML_a, XML_lstStyle));
2196     pFS->startElement(FSNS(XML_a, XML_p));
2197 
2198     pFS->startElement(FSNS(XML_a, XML_pPr));
2199 
2200     bool bDummy = false;
2201     sal_Int32 nDummy;
2202     WriteRunProperties(xPropSet, false, XML_defRPr, true, bDummy, nDummy );
2203 
2204     pFS->endElement( FSNS( XML_a, XML_pPr ) );
2205 
2206     for (const uno::Reference<chart2::XFormattedString>& rxFS : xFormattedTitle)
2207     {
2208         pFS->startElement(FSNS(XML_a, XML_r));
2209         bDummy = false;
2210         Reference< beans::XPropertySet > xRunPropSet(rxFS, uno::UNO_QUERY);
2211         WriteRunProperties(xRunPropSet, false, XML_rPr, true, bDummy, nDummy);
2212         pFS->startElement(FSNS(XML_a, XML_t));
2213 
2214         // the linebreak should always be at the end of the XFormattedString text
2215         bool bNextPara = rxFS->getString().endsWith(u"\n");
2216         if (!bNextPara)
2217             pFS->writeEscaped(rxFS->getString());
2218         else
2219         {
2220             sal_Int32 nEnd = rxFS->getString().lastIndexOf('\n');
2221             pFS->writeEscaped(rxFS->getString().replaceAt(nEnd, 1, u""));
2222         }
2223         pFS->endElement(FSNS(XML_a, XML_t));
2224         pFS->endElement(FSNS(XML_a, XML_r));
2225 
2226         if (bNextPara)
2227         {
2228             pFS->endElement(FSNS(XML_a, XML_p));
2229 
2230             pFS->startElement(FSNS(XML_a, XML_p));
2231             pFS->startElement(FSNS(XML_a, XML_pPr));
2232             bDummy = false;
2233             WriteRunProperties(xPropSet, false, XML_defRPr, true, bDummy, nDummy);
2234             pFS->endElement(FSNS(XML_a, XML_pPr));
2235         }
2236     }
2237 
2238     pFS->endElement( FSNS( XML_a, XML_p ) );
2239 
2240     if (bIsChartex) {
2241         pFS->endElement( FSNS( XML_cx, XML_txPr ) );
2242     } else {
2243         pFS->endElement( FSNS( XML_c, XML_rich ) );
2244         pFS->endElement( FSNS( XML_c, XML_tx ) );
2245     }
2246 
2247     uno::Any aManualLayout = xPropSet->getPropertyValue(u"RelativePosition"_ustr);
2248     if (aManualLayout.hasValue())
2249     {
2250         if (bIsChartex) {
2251             // TODO. Chartex doesn't have a manualLayout tag, but does have
2252             // "pos" and "align" attributes. Not sure how these correspond.
2253         } else {
2254             pFS->startElement(FSNS(XML_c, XML_layout));
2255             pFS->startElement(FSNS(XML_c, XML_manualLayout));
2256             pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
2257             pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
2258 
2259             Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
2260             awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
2261 
2262             awt::Size aSize = xShape->getSize();
2263             awt::Point aPos2 = xShape->getPosition();
2264             // rotated shapes need special handling...
2265             double fSin = fabs(sin(basegfx::deg2rad<100>(nRotation)));
2266             // remove part of height from X direction, if title is rotated down
2267             if( nRotation*0.01 > 180.0 )
2268                 aPos2.X -= static_cast<sal_Int32>(fSin * aSize.Height + 0.5);
2269             // remove part of width from Y direction, if title is rotated up
2270             else if( nRotation*0.01 > 0.0 )
2271                 aPos2.Y -= static_cast<sal_Int32>(fSin * aSize.Width + 0.5);
2272 
2273             double x = static_cast<double>(aPos2.X) / static_cast<double>(aPageSize.Width);
2274             double y = static_cast<double>(aPos2.Y) / static_cast<double>(aPageSize.Height);
2275             /*
2276             pFS->singleElement(FSNS(XML_c, XML_wMode), XML_val, "edge");
2277             pFS->singleElement(FSNS(XML_c, XML_hMode), XML_val, "edge");
2278                     */
2279             pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
2280             pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
2281             /*
2282             pFS->singleElement(FSNS(XML_c, XML_w), XML_val, "");
2283             pFS->singleElement(FSNS(XML_c, XML_h), XML_val, "");
2284                     */
2285             pFS->endElement(FSNS(XML_c, XML_manualLayout));
2286             pFS->endElement(FSNS(XML_c, XML_layout));
2287         }
2288     }
2289 
2290     if (!bIsChartex) {
2291         pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0");
2292     }
2293 
2294     if (!bIsChartex) {
2295         // shape properties
2296         if( xPropSet.is() )
2297         {
2298             exportShapeProps( xPropSet, bIsChartex );
2299         }
2300     }
2301 
2302     if (bIsChartex) {
2303         pFS->endElement( FSNS( XML_cx, XML_title ) );
2304     } else {
2305         pFS->endElement( FSNS( XML_c, XML_title ) );
2306     }
2307 }
2308 
exportPlotArea(const Reference<css::chart::XChartDocument> & xChartDoc,bool bIsChartex)2309 void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >& xChartDoc,
2310         bool bIsChartex)
2311 {
2312     Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, uno::UNO_QUERY );
2313     if( ! xBCooSysCnt.is())
2314         return;
2315 
2316     // plot-area element
2317 
2318     FSHelperPtr pFS = GetFS();
2319 
2320     if (bIsChartex) {
2321         pFS->startElement(FSNS(XML_cx, XML_plotArea));
2322         pFS->startElement(FSNS(XML_cx, XML_plotAreaRegion));
2323     } else {
2324         pFS->startElement(FSNS(XML_c, XML_plotArea));
2325 
2326         Reference<beans::XPropertySet> xWall(mxNewDiagram, uno::UNO_QUERY);
2327         if( xWall.is() )
2328         {
2329             uno::Any aAny = xWall->getPropertyValue(u"RelativePosition"_ustr);
2330             if (aAny.hasValue())
2331             {
2332                 chart2::RelativePosition aPos = aAny.get<chart2::RelativePosition>();
2333                 aAny = xWall->getPropertyValue(u"RelativeSize"_ustr);
2334                 chart2::RelativeSize aSize = aAny.get<chart2::RelativeSize>();
2335                 uno::Reference< css::chart::XDiagramPositioning > xDiagramPositioning( xChartDoc->getDiagram(), uno::UNO_QUERY );
2336                 exportManualLayout(aPos, aSize, xDiagramPositioning->isExcludingDiagramPositioning() );
2337             }
2338         }
2339     }
2340 
2341     // chart type
2342     const Sequence< Reference< chart2::XCoordinateSystem > >
2343         aCooSysSeq( xBCooSysCnt->getCoordinateSystems());
2344 
2345     // tdf#123647 Save empty chart as empty bar chart.
2346     if (!aCooSysSeq.hasElements())
2347     {
2348         assert(!bIsChartex);
2349 
2350         pFS->startElement(FSNS(XML_c, XML_barChart));
2351         pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, "col");
2352         pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, "clustered");
2353         pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
2354         createAxes(true, false);
2355         pFS->endElement(FSNS(XML_c, XML_barChart));
2356     }
2357 
2358     for( const auto& rCS : aCooSysSeq )
2359     {
2360         Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY );
2361         if( ! xCTCnt.is())
2362             continue;
2363         mnSeriesCount=0;
2364         const Sequence< Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes());
2365         for( const auto& rCT : aCTSeq )
2366         {
2367             Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, uno::UNO_QUERY );
2368             if( ! xDSCnt.is())
2369                 return;
2370             Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY );
2371             if( ! xChartType.is())
2372                 continue;
2373             // note: if xDSCnt.is() then also aCTSeq[nCTIdx]
2374             OUString aChartType( xChartType->getChartType());
2375             sal_Int32 eChartType = lcl_getChartType( aChartType );
2376             switch( eChartType )
2377             {
2378                 case chart::TYPEID_BAR:
2379                     {
2380                         exportBarChart( xChartType );
2381                         break;
2382                     }
2383                 case chart::TYPEID_AREA:
2384                     {
2385                         exportAreaChart( xChartType );
2386                         break;
2387                     }
2388                 case chart::TYPEID_LINE:
2389                     {
2390                         exportLineChart( xChartType );
2391                         break;
2392                     }
2393                 case chart::TYPEID_BUBBLE:
2394                     {
2395                         exportBubbleChart( xChartType );
2396                         break;
2397                     }
2398                 case chart::TYPEID_FUNNEL:
2399                     {
2400                         exportFunnelChart( xChartType );
2401                         break;
2402                     }
2403                 case chart::TYPEID_DOUGHNUT: // doesn't currently happen
2404                 case chart::TYPEID_OFPIE:    // doesn't currently happen
2405                 case chart::TYPEID_PIE:
2406                     {
2407                         sal_Int32 eCT = getChartType( );
2408                         if(eCT == chart::TYPEID_DOUGHNUT)
2409                         {
2410                             exportDoughnutChart( xChartType );
2411                         }
2412                         else
2413                         {
2414 
2415                             PropertySet xChartTypeProp(rCT);
2416                             chart2::PieChartSubType subtype(chart2::PieChartSubType_NONE);
2417                             if (!xChartTypeProp.getProperty(subtype, PROP_SubPieType))
2418                             {
2419                                 subtype = chart2::PieChartSubType_NONE;
2420                             }
2421                             if (subtype != chart2::PieChartSubType_NONE)
2422                             {
2423                                 const char* sSubType = "pie";   // default
2424                                 switch (subtype) {
2425                                     case chart2::PieChartSubType_PIE:
2426                                         sSubType = "pie";
2427                                         break;
2428                                     case chart2::PieChartSubType_BAR:
2429                                         sSubType = "bar";
2430                                         break;
2431                                     default:
2432                                         assert(false);
2433                                 }
2434                                 double fSplitPos;
2435                                 if (!xChartTypeProp.getProperty(fSplitPos,
2436                                             PROP_SplitPos)) {
2437                                     fSplitPos = 2;
2438                                 }
2439 
2440                                 exportOfPieChart(xChartType, sSubType, fSplitPos);
2441                             } else {
2442                                 exportPieChart( xChartType );
2443                             }
2444                         }
2445                         break;
2446                     }
2447                 case chart::TYPEID_RADARLINE:
2448                 case chart::TYPEID_RADARAREA:
2449                     {
2450                         exportRadarChart( xChartType );
2451                         break;
2452                     }
2453                 case chart::TYPEID_SCATTER:
2454                     {
2455                         exportScatterChart( xChartType );
2456                         break;
2457                     }
2458                 case chart::TYPEID_STOCK:
2459                     {
2460                         exportStockChart( xChartType );
2461                         break;
2462                     }
2463                 case chart::TYPEID_SURFACE:
2464                     {
2465                         exportSurfaceChart( xChartType );
2466                         break;
2467                     }
2468                 default:
2469                     {
2470                         SAL_WARN("oox", "ChartExport::exportPlotArea -- not support chart type");
2471                         break;
2472                     }
2473             }
2474 
2475         }
2476     }
2477 
2478     if (bIsChartex) {
2479         pFS->endElement( FSNS( XML_cx, XML_plotAreaRegion ) );
2480     }
2481 
2482     //Axis Data
2483     exportAxes(bIsChartex);
2484 
2485     if (!bIsChartex) {
2486         // Data Table
2487         // not supported in chartex?
2488         exportDataTable();
2489     }
2490 
2491     // shape properties
2492     /*
2493      * Export the Plot area Shape Properties
2494      * eg: Fill and Outline
2495      */
2496     Reference< css::chart::X3DDisplay > xWallFloorSupplier( mxDiagram, uno::UNO_QUERY );
2497     // tdf#114139 For 2D charts Plot Area equivalent is Chart Wall.
2498     // Unfortunately LibreOffice doesn't have Plot Area equivalent for 3D charts.
2499     // It means that Plot Area couldn't be displayed and changed for 3D chars in LibreOffice.
2500     // We cannot write Wall attributes into Plot Area for 3D charts, because Wall us used as background wall.
2501     if( !mbIs3DChart && xWallFloorSupplier.is() )
2502     {
2503         Reference< beans::XPropertySet > xWallPropSet = xWallFloorSupplier->getWall();
2504         if( xWallPropSet.is() )
2505         {
2506             uno::Any aAny = xWallPropSet->getPropertyValue(u"LineStyle"_ustr);
2507             sal_Int32 eChartType = getChartType( );
2508             // Export LineStyle_NONE instead of default linestyle of PlotArea border, because LibreOffice
2509             // make invisible the Wall shape properties, in case of these charts. Or in the future set
2510             // the default LineStyle of these charts to LineStyle_NONE.
2511             bool noSupportWallProp = ( (eChartType == chart::TYPEID_PIE) || (eChartType == chart::TYPEID_RADARLINE) || (eChartType == chart::TYPEID_RADARAREA) );
2512             if ( noSupportWallProp && (aAny != drawing::LineStyle_NONE) )
2513             {
2514                 xWallPropSet->setPropertyValue( u"LineStyle"_ustr, uno::Any(drawing::LineStyle_NONE) );
2515             }
2516             exportShapeProps( xWallPropSet, bIsChartex );
2517         }
2518     }
2519 
2520     if (bIsChartex) {
2521         pFS->endElement( FSNS( XML_cx, XML_plotArea ) );
2522     } else {
2523         pFS->endElement( FSNS( XML_c, XML_plotArea ) );
2524     }
2525 
2526 }
2527 
exportManualLayout(const css::chart2::RelativePosition & rPos,const css::chart2::RelativeSize & rSize,const bool bIsExcludingDiagramPositioning)2528 void ChartExport::exportManualLayout(const css::chart2::RelativePosition& rPos,
2529                                      const css::chart2::RelativeSize& rSize,
2530                                      const bool bIsExcludingDiagramPositioning)
2531 {
2532     // 2006 chart schema only
2533     FSHelperPtr pFS = GetFS();
2534     pFS->startElement(FSNS(XML_c, XML_layout));
2535     pFS->startElement(FSNS(XML_c, XML_manualLayout));
2536 
2537     // By default layoutTarget is set to "outer" and we shouldn't save it in that case
2538     if ( bIsExcludingDiagramPositioning )
2539     {
2540         pFS->singleElement(FSNS(XML_c, XML_layoutTarget), XML_val, "inner");
2541     }
2542     pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
2543     pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
2544 
2545     double x = rPos.Primary;
2546     double y = rPos.Secondary;
2547     const double w = rSize.Primary;
2548     const double h = rSize.Secondary;
2549     switch (rPos.Anchor)
2550     {
2551         case drawing::Alignment_LEFT:
2552             y -= (h/2);
2553         break;
2554         case drawing::Alignment_TOP_LEFT:
2555         break;
2556         case drawing::Alignment_BOTTOM_LEFT:
2557             y -= h;
2558         break;
2559         case drawing::Alignment_TOP:
2560             x -= (w/2);
2561         break;
2562         case drawing::Alignment_CENTER:
2563             x -= (w/2);
2564             y -= (h/2);
2565         break;
2566         case drawing::Alignment_BOTTOM:
2567             x -= (w/2);
2568             y -= h;
2569         break;
2570         case drawing::Alignment_TOP_RIGHT:
2571             x -= w;
2572         break;
2573         case drawing::Alignment_BOTTOM_RIGHT:
2574             x -= w;
2575             y -= h;
2576         break;
2577         case drawing::Alignment_RIGHT:
2578             y -= (h/2);
2579             x -= w;
2580         break;
2581         default:
2582             SAL_WARN("oox", "unhandled alignment case for manual layout export " << static_cast<sal_uInt16>(rPos.Anchor));
2583     }
2584 
2585     pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
2586 
2587     pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
2588 
2589     pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
2590 
2591     pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
2592 
2593     pFS->endElement(FSNS(XML_c, XML_manualLayout));
2594     pFS->endElement(FSNS(XML_c, XML_layout));
2595 }
2596 
exportFill(const Reference<XPropertySet> & xPropSet)2597 void ChartExport::exportFill( const Reference< XPropertySet >& xPropSet )
2598 {
2599     // Similar to DrawingML::WriteFill, but gradient access via name
2600     if (!GetProperty( xPropSet, u"FillStyle"_ustr ))
2601         return;
2602     FillStyle aFillStyle(FillStyle_NONE);
2603     mAny >>= aFillStyle;
2604 
2605     // map full transparent background to no fill
2606     if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, u"FillTransparence"_ustr ))
2607     {
2608         sal_Int16 nVal = 0;
2609         mAny >>= nVal;
2610         if ( nVal == 100 )
2611             aFillStyle = FillStyle_NONE;
2612     }
2613     OUString sFillTransparenceGradientName;
2614     if (aFillStyle == FillStyle_SOLID
2615         && GetProperty(xPropSet, u"FillTransparenceGradientName"_ustr) && (mAny >>= sFillTransparenceGradientName)
2616         && !sFillTransparenceGradientName.isEmpty())
2617     {
2618         awt::Gradient aTransparenceGradient;
2619         uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
2620         uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance(u"com.sun.star.drawing.TransparencyGradientTable"_ustr), uno::UNO_QUERY);
2621         uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName);
2622         rTransparenceValue >>= aTransparenceGradient;
2623         if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff)
2624             aFillStyle = FillStyle_NONE;
2625     }
2626     switch( aFillStyle )
2627     {
2628         case FillStyle_SOLID:
2629             exportSolidFill(xPropSet);
2630             break;
2631         case FillStyle_GRADIENT :
2632             exportGradientFill( xPropSet );
2633             break;
2634         case FillStyle_BITMAP :
2635             exportBitmapFill( xPropSet );
2636             break;
2637         case FillStyle_HATCH:
2638             exportHatch(xPropSet);
2639             break;
2640         case FillStyle_NONE:
2641             mpFS->singleElementNS(XML_a, XML_noFill);
2642             break;
2643         default:
2644             ;
2645     }
2646 }
2647 
exportSolidFill(const Reference<XPropertySet> & xPropSet)2648 void ChartExport::exportSolidFill(const Reference< XPropertySet >& xPropSet)
2649 {
2650     // Similar to DrawingML::WriteSolidFill, but gradient access via name
2651     // and currently no InteropGrabBag
2652     // get fill color
2653     sal_uInt32 nFillColor = 0;
2654     if (!GetProperty(xPropSet, u"FillColor"_ustr) || !(mAny >>= nFillColor))
2655         return;
2656 
2657     sal_Int32 nAlpha = MAX_PERCENT;
2658     if (GetProperty( xPropSet, u"FillTransparence"_ustr ))
2659     {
2660         sal_Int32 nTransparency = 0;
2661         mAny >>= nTransparency;
2662         // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
2663         nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
2664     }
2665     // OOXML has no separate transparence gradient but uses transparency in the gradient stops.
2666     // So we merge transparency and color and use gradient fill in such case.
2667     basegfx::BGradient aTransparenceGradient;
2668     bool bNeedGradientFill(false);
2669     OUString sFillTransparenceGradientName;
2670 
2671     if (GetProperty(xPropSet, u"FillTransparenceGradientName"_ustr)
2672         && (mAny >>= sFillTransparenceGradientName)
2673         && !sFillTransparenceGradientName.isEmpty())
2674     {
2675         uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
2676         uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance(u"com.sun.star.drawing.TransparencyGradientTable"_ustr), uno::UNO_QUERY);
2677         const uno::Any rTransparenceAny = xTransparenceGradient->getByName(sFillTransparenceGradientName);
2678 
2679         aTransparenceGradient = model::gradient::getFromAny(rTransparenceAny);
2680         basegfx::BColor aSingleColor;
2681         bNeedGradientFill = !aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor);
2682 
2683         if (!bNeedGradientFill)
2684         {
2685             // Our alpha is a single gray color value.
2686             const sal_uInt8 nRed(aSingleColor.getRed() * 255.0);
2687 
2688             // drawingML alpha is a percentage on a 0..100000 scale.
2689             nAlpha = (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
2690         }
2691     }
2692     // write XML
2693     if (bNeedGradientFill)
2694     {
2695         // no longer create copy/PseudoColorGradient, use new API of
2696         // WriteGradientFill to express fix fill color
2697         mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
2698         WriteGradientFill(nullptr, nFillColor, &aTransparenceGradient);
2699         mpFS->endElementNS(XML_a, XML_gradFill);
2700     }
2701     else
2702         WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
2703 }
2704 
exportHatch(const Reference<XPropertySet> & xPropSet)2705 void ChartExport::exportHatch( const Reference< XPropertySet >& xPropSet )
2706 {
2707     if (!xPropSet.is())
2708         return;
2709 
2710     if (GetProperty(xPropSet, u"FillHatchName"_ustr))
2711     {
2712         OUString aHatchName;
2713         mAny >>= aHatchName;
2714         uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
2715         uno::Reference< container::XNameAccess > xHatchTable( xFact->createInstance(u"com.sun.star.drawing.HatchTable"_ustr), uno::UNO_QUERY );
2716         uno::Any rValue = xHatchTable->getByName(aHatchName);
2717         css::drawing::Hatch aHatch;
2718         rValue >>= aHatch;
2719         WritePattFill(xPropSet, aHatch);
2720     }
2721 
2722 }
2723 
exportBitmapFill(const Reference<XPropertySet> & xPropSet)2724 void ChartExport::exportBitmapFill( const Reference< XPropertySet >& xPropSet )
2725 {
2726     if( !xPropSet.is() )
2727         return;
2728 
2729     OUString sFillBitmapName;
2730     xPropSet->getPropertyValue(u"FillBitmapName"_ustr) >>= sFillBitmapName;
2731 
2732     uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
2733     try
2734     {
2735         uno::Reference< container::XNameAccess > xBitmapTable( xFact->createInstance(u"com.sun.star.drawing.BitmapTable"_ustr), uno::UNO_QUERY );
2736         uno::Any rValue = xBitmapTable->getByName( sFillBitmapName );
2737         if (rValue.has<uno::Reference<awt::XBitmap>>())
2738         {
2739             uno::Reference<awt::XBitmap> xBitmap = rValue.get<uno::Reference<awt::XBitmap>>();
2740             uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY);
2741             if (xGraphic.is())
2742             {
2743                 WriteXGraphicBlipFill(xPropSet, xGraphic, XML_a, true, true);
2744             }
2745         }
2746     }
2747     catch (const uno::Exception &)
2748     {
2749         TOOLS_WARN_EXCEPTION("oox", "ChartExport::exportBitmapFill");
2750     }
2751 }
2752 
exportGradientFill(const Reference<XPropertySet> & xPropSet)2753 void ChartExport::exportGradientFill( const Reference< XPropertySet >& xPropSet )
2754 {
2755     if( !xPropSet.is() )
2756         return;
2757 
2758     OUString sFillGradientName;
2759     xPropSet->getPropertyValue(u"FillGradientName"_ustr) >>= sFillGradientName;
2760 
2761     uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
2762     try
2763     {
2764         uno::Reference< container::XNameAccess > xGradient( xFact->createInstance(u"com.sun.star.drawing.GradientTable"_ustr), uno::UNO_QUERY );
2765         const uno::Any rGradientAny(xGradient->getByName( sFillGradientName ));
2766         const basegfx::BGradient aGradient = model::gradient::getFromAny(rGradientAny);
2767         basegfx::BColor aSingleColor;
2768 
2769         if (!aGradient.GetColorStops().isSingleColor(aSingleColor))
2770         {
2771             basegfx::BGradient aTransparenceGradient;
2772             mpFS->startElementNS(XML_a, XML_gradFill);
2773             OUString sFillTransparenceGradientName;
2774 
2775             if( (xPropSet->getPropertyValue(u"FillTransparenceGradientName"_ustr) >>= sFillTransparenceGradientName) && !sFillTransparenceGradientName.isEmpty())
2776             {
2777                 uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance(u"com.sun.star.drawing.TransparencyGradientTable"_ustr), uno::UNO_QUERY);
2778                 const uno::Any rTransparenceAny(xTransparenceGradient->getByName(sFillTransparenceGradientName));
2779 
2780                 aTransparenceGradient = model::gradient::getFromAny(rTransparenceAny);
2781 
2782                 WriteGradientFill(&aGradient, 0, &aTransparenceGradient);
2783             }
2784             else if (GetProperty(xPropSet, u"FillTransparence"_ustr) )
2785             {
2786                 // no longer create PseudoTransparencyGradient, use new API of
2787                 // WriteGradientFill to express fix transparency
2788                 sal_Int32 nTransparency = 0;
2789                 mAny >>= nTransparency;
2790                 // nTransparency is [0..100]%
2791                 WriteGradientFill(&aGradient, 0, nullptr, nTransparency * 0.01);
2792             }
2793             else
2794             {
2795                 WriteGradientFill(&aGradient, 0, nullptr);
2796             }
2797 
2798             mpFS->endElementNS(XML_a, XML_gradFill);
2799         }
2800     }
2801     catch (const uno::Exception &)
2802     {
2803         TOOLS_INFO_EXCEPTION("oox", "ChartExport::exportGradientFill");
2804     }
2805 }
2806 
exportDataTable()2807 void ChartExport::exportDataTable( )
2808 {
2809     // Not supported in chartex 2014 schema
2810     auto xDataTable = mxNewDiagram->getDataTable();
2811     if (!xDataTable.is())
2812         return;
2813 
2814     FSHelperPtr pFS = GetFS();
2815     uno::Reference<beans::XPropertySet> aPropSet(xDataTable, uno::UNO_QUERY);
2816 
2817     bool bShowVBorder = false;
2818     bool bShowHBorder = false;
2819     bool bShowOutline = false;
2820     bool bShowKeys = false;
2821 
2822     if (GetProperty(aPropSet, u"HBorder"_ustr))
2823         mAny >>= bShowHBorder;
2824     if (GetProperty(aPropSet, u"VBorder"_ustr))
2825         mAny >>= bShowVBorder;
2826     if (GetProperty(aPropSet, u"Outline"_ustr))
2827         mAny >>= bShowOutline;
2828     if (GetProperty(aPropSet, u"Keys"_ustr))
2829         mAny >>= bShowKeys;
2830 
2831     pFS->startElement(FSNS(XML_c, XML_dTable));
2832 
2833     if (bShowHBorder)
2834         pFS->singleElement(FSNS(XML_c, XML_showHorzBorder), XML_val, "1" );
2835     if (bShowVBorder)
2836         pFS->singleElement(FSNS(XML_c, XML_showVertBorder), XML_val, "1");
2837     if (bShowOutline)
2838         pFS->singleElement(FSNS(XML_c, XML_showOutline), XML_val, "1");
2839     if (bShowKeys)
2840         pFS->singleElement(FSNS(XML_c, XML_showKeys), XML_val, "1");
2841 
2842     exportShapeProps(aPropSet, false);
2843     exportTextProps(aPropSet, false);
2844 
2845     pFS->endElement(FSNS(XML_c, XML_dTable));
2846 }
2847 
exportAreaChart(const Reference<chart2::XChartType> & xChartType)2848 void ChartExport::exportAreaChart( const Reference< chart2::XChartType >& xChartType )
2849 {
2850     FSHelperPtr pFS = GetFS();
2851     const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2852     for (const auto& splitDataSeries : aSplitDataSeries)
2853     {
2854         if (!splitDataSeries.hasElements())
2855             continue;
2856 
2857         sal_Int32 nTypeId = XML_areaChart;
2858         if (mbIs3DChart)
2859             nTypeId = XML_area3DChart;
2860         pFS->startElement(FSNS(XML_c, nTypeId));
2861 
2862         exportGrouping();
2863         bool bPrimaryAxes = true;
2864         exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
2865         createAxes(bPrimaryAxes, false);
2866         //exportAxesId(bPrimaryAxes);
2867 
2868         pFS->endElement(FSNS(XML_c, nTypeId));
2869     }
2870 }
2871 
exportBarChart(const Reference<chart2::XChartType> & xChartType)2872 void ChartExport::exportBarChart(const Reference< chart2::XChartType >& xChartType)
2873 {
2874     sal_Int32 nTypeId = XML_barChart;
2875     if (mbIs3DChart)
2876         nTypeId = XML_bar3DChart;
2877     FSHelperPtr pFS = GetFS();
2878 
2879     const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2880     for (const auto& splitDataSeries : aSplitDataSeries)
2881     {
2882         if (!splitDataSeries.hasElements())
2883             continue;
2884 
2885         pFS->startElement(FSNS(XML_c, nTypeId));
2886         // bar direction
2887         bool bVertical = false;
2888         Reference< XPropertySet > xPropSet(mxDiagram, uno::UNO_QUERY);
2889         if (GetProperty(xPropSet, u"Vertical"_ustr))
2890             mAny >>= bVertical;
2891 
2892         const char* bardir = bVertical ? "bar" : "col";
2893         pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, bardir);
2894 
2895         exportGrouping(true);
2896 
2897         exportVaryColors(xChartType);
2898 
2899         bool bPrimaryAxes = true;
2900         exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
2901 
2902         Reference< XPropertySet > xTypeProp(xChartType, uno::UNO_QUERY);
2903 
2904         if (xTypeProp.is() && GetProperty(xTypeProp, u"GapwidthSequence"_ustr))
2905         {
2906             uno::Sequence< sal_Int32 > aBarPositionSequence;
2907             mAny >>= aBarPositionSequence;
2908             if (aBarPositionSequence.hasElements())
2909             {
2910                 sal_Int32 nGapWidth = aBarPositionSequence[0];
2911                 pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(nGapWidth));
2912             }
2913         }
2914 
2915         if (mbIs3DChart)
2916         {
2917             // Shape
2918             namespace cssc = css::chart;
2919             sal_Int32 nGeom3d = cssc::ChartSolidType::RECTANGULAR_SOLID;
2920             if (xPropSet.is() && GetProperty(xPropSet, u"SolidType"_ustr))
2921                 mAny >>= nGeom3d;
2922             const char* sShapeType = nullptr;
2923             switch (nGeom3d)
2924             {
2925             case cssc::ChartSolidType::RECTANGULAR_SOLID:
2926                 sShapeType = "box";
2927                 break;
2928             case cssc::ChartSolidType::CONE:
2929                 sShapeType = "cone";
2930                 break;
2931             case cssc::ChartSolidType::CYLINDER:
2932                 sShapeType = "cylinder";
2933                 break;
2934             case cssc::ChartSolidType::PYRAMID:
2935                 sShapeType = "pyramid";
2936                 break;
2937             }
2938             pFS->singleElement(FSNS(XML_c, XML_shape), XML_val, sShapeType);
2939         }
2940 
2941         //overlap
2942         if (!mbIs3DChart && xTypeProp.is() && GetProperty(xTypeProp, u"OverlapSequence"_ustr))
2943         {
2944             uno::Sequence< sal_Int32 > aBarPositionSequence;
2945             mAny >>= aBarPositionSequence;
2946             if (aBarPositionSequence.hasElements())
2947             {
2948                 sal_Int32 nOverlap = aBarPositionSequence[0];
2949                 // Stacked/Percent Bar/Column chart Overlap-workaround
2950                 // Export the Overlap value with 100% for stacked charts,
2951                 // because the default overlap value of the Bar/Column chart is 0% and
2952                 // LibreOffice do nothing with the overlap value in Stacked charts case,
2953                 // unlike the MS Office, which is interpreted differently.
2954                 if ((mbStacked || mbPercent) && nOverlap != 100)
2955                 {
2956                     nOverlap = 100;
2957                     pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
2958                 }
2959                 else // Normal bar chart
2960                 {
2961                     pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
2962                 }
2963             }
2964         }
2965 
2966         createAxes(bPrimaryAxes, false);
2967 
2968         pFS->endElement(FSNS(XML_c, nTypeId));
2969     }
2970 }
2971 
exportBubbleChart(const Reference<chart2::XChartType> & xChartType)2972 void ChartExport::exportBubbleChart( const Reference< chart2::XChartType >& xChartType )
2973 {
2974     FSHelperPtr pFS = GetFS();
2975     const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2976     for (const auto& splitDataSeries : aSplitDataSeries)
2977     {
2978         if (!splitDataSeries.hasElements())
2979             continue;
2980 
2981         pFS->startElement(FSNS(XML_c, XML_bubbleChart));
2982 
2983         exportVaryColors(xChartType);
2984 
2985         bool bPrimaryAxes = true;
2986         exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
2987 
2988         createAxes(bPrimaryAxes, false);
2989 
2990         pFS->endElement(FSNS(XML_c, XML_bubbleChart));
2991     }
2992 }
2993 
exportFunnelChart(const Reference<chart2::XChartType> & xChartType)2994 void ChartExport::exportFunnelChart( const Reference< chart2::XChartType >& xChartType )
2995 {
2996     FSHelperPtr pFS = GetFS();
2997     const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2998     for (const auto& splitDataSeries : aSplitDataSeries)
2999     {
3000         if (!splitDataSeries.hasElements())
3001             continue;
3002 
3003         //exportVaryColors(xChartType);
3004 
3005         exportSeries_chartex(xChartType, splitDataSeries, "funnel");
3006     }
3007 }
3008 
exportDoughnutChart(const Reference<chart2::XChartType> & xChartType)3009 void ChartExport::exportDoughnutChart( const Reference< chart2::XChartType >& xChartType )
3010 {
3011     FSHelperPtr pFS = GetFS();
3012     pFS->startElement(FSNS(XML_c, XML_doughnutChart));
3013 
3014     exportVaryColors(xChartType);
3015 
3016     bool bPrimaryAxes = true;
3017     exportAllSeries(xChartType, bPrimaryAxes);
3018     // firstSliceAng
3019     exportFirstSliceAng( );
3020     //FIXME: holeSize
3021     pFS->singleElement(FSNS(XML_c, XML_holeSize), XML_val, OString::number(50));
3022 
3023     pFS->endElement( FSNS( XML_c, XML_doughnutChart ) );
3024 }
3025 
exportOfPieChart(const Reference<chart2::XChartType> & xChartType,const char * sSubType,double fSplitPos)3026 void ChartExport::exportOfPieChart(
3027         const Reference< chart2::XChartType >& xChartType,
3028         const char* sSubType,
3029         double fSplitPos)
3030 {
3031     FSHelperPtr pFS = GetFS();
3032     pFS->startElement(FSNS(XML_c, XML_ofPieChart));
3033 
3034     pFS->singleElement(FSNS(XML_c, XML_ofPieType), XML_val, sSubType);
3035 
3036     exportVaryColors(xChartType);
3037 
3038     bool bPrimaryAxes = true;
3039     exportAllSeries(xChartType, bPrimaryAxes);
3040 
3041     pFS->singleElement(FSNS(XML_c, XML_splitType), XML_val, "pos");
3042     pFS->singleElement(FSNS(XML_c, XML_splitPos), XML_val, OString::number(fSplitPos));
3043 
3044     pFS->endElement( FSNS( XML_c, XML_ofPieChart ) );
3045 }
3046 
3047 namespace {
3048 
writeDataLabelsRange(const FSHelperPtr & pFS,const XmlFilterBase * pFB,DataLabelsRange & rDLblsRange)3049 void writeDataLabelsRange(const FSHelperPtr& pFS, const XmlFilterBase* pFB, DataLabelsRange& rDLblsRange)
3050 {
3051     if (rDLblsRange.empty())
3052         return;
3053 
3054     pFS->startElement(FSNS(XML_c, XML_extLst));
3055     pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{02D57815-91ED-43cb-92C2-25804820EDAC}", FSNS(XML_xmlns, XML_c15), pFB->getNamespaceURL(OOX_NS(c15)));
3056     pFS->startElement(FSNS(XML_c15, XML_datalabelsRange));
3057 
3058     // Write cell range.
3059     pFS->startElement(FSNS(XML_c15, XML_f));
3060     pFS->writeEscaped(rDLblsRange.getRange());
3061     pFS->endElement(FSNS(XML_c15, XML_f));
3062 
3063     // Write all labels.
3064     pFS->startElement(FSNS(XML_c15, XML_dlblRangeCache));
3065     pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(rDLblsRange.count()));
3066     for (const auto& rLabelKV: rDLblsRange)
3067     {
3068         pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(rLabelKV.first));
3069         pFS->startElement(FSNS(XML_c, XML_v));
3070         pFS->writeEscaped(rLabelKV.second);
3071         pFS->endElement(FSNS( XML_c, XML_v ));
3072         pFS->endElement(FSNS(XML_c, XML_pt));
3073     }
3074 
3075     pFS->endElement(FSNS(XML_c15, XML_dlblRangeCache));
3076 
3077     pFS->endElement(FSNS(XML_c15, XML_datalabelsRange));
3078     pFS->endElement(FSNS(XML_c, XML_ext));
3079     pFS->endElement(FSNS(XML_c, XML_extLst));
3080 }
3081 
3082 }
3083 
exportLineChart(const Reference<chart2::XChartType> & xChartType)3084 void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChartType )
3085 {
3086     FSHelperPtr pFS = GetFS();
3087     const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
3088     for (const auto& splitDataSeries : aSplitDataSeries)
3089     {
3090         if (!splitDataSeries.hasElements())
3091             continue;
3092 
3093         sal_Int32 nTypeId = XML_lineChart;
3094         if( mbIs3DChart )
3095             nTypeId = XML_line3DChart;
3096         pFS->startElement(FSNS(XML_c, nTypeId));
3097 
3098         exportGrouping( );
3099 
3100         exportVaryColors(xChartType);
3101         // TODO: show marker symbol in series?
3102         bool bPrimaryAxes = true;
3103         exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
3104 
3105         // show marker?
3106         sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
3107         Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
3108         if( GetProperty( xPropSet, u"SymbolType"_ustr ) )
3109             mAny >>= nSymbolType;
3110 
3111         if( !mbIs3DChart )
3112         {
3113             exportHiLowLines();
3114             exportUpDownBars(xChartType);
3115             const char* marker = nSymbolType == css::chart::ChartSymbolType::NONE? "0":"1";
3116             pFS->singleElement(FSNS(XML_c, XML_marker), XML_val, marker);
3117         }
3118 
3119         createAxes(bPrimaryAxes, true);
3120 
3121         pFS->endElement( FSNS( XML_c, nTypeId ) );
3122     }
3123 }
3124 
exportPieChart(const Reference<chart2::XChartType> & xChartType)3125 void ChartExport::exportPieChart( const Reference< chart2::XChartType >& xChartType )
3126 {
3127     FSHelperPtr pFS = GetFS();
3128     sal_Int32 nTypeId = XML_pieChart;
3129     if( mbIs3DChart )
3130         nTypeId = XML_pie3DChart;
3131     pFS->startElement(FSNS(XML_c, nTypeId));
3132 
3133     exportVaryColors(xChartType);
3134 
3135     bool bPrimaryAxes = true;
3136     exportAllSeries(xChartType, bPrimaryAxes);
3137 
3138     if( !mbIs3DChart )
3139     {
3140         // firstSliceAng
3141         exportFirstSliceAng( );
3142     }
3143 
3144     pFS->endElement( FSNS( XML_c, nTypeId ) );
3145 }
3146 
exportRadarChart(const Reference<chart2::XChartType> & xChartType)3147 void ChartExport::exportRadarChart( const Reference< chart2::XChartType >& xChartType)
3148 {
3149     FSHelperPtr pFS = GetFS();
3150     pFS->startElement(FSNS(XML_c, XML_radarChart));
3151 
3152     // radarStyle
3153     sal_Int32 eChartType = getChartType( );
3154     const char* radarStyle = nullptr;
3155     if( eChartType == chart::TYPEID_RADARAREA )
3156         radarStyle = "filled";
3157     else
3158         radarStyle = "marker";
3159     pFS->singleElement(FSNS(XML_c, XML_radarStyle), XML_val, radarStyle);
3160 
3161     exportVaryColors(xChartType);
3162     bool bPrimaryAxes = true;
3163     exportAllSeries(xChartType, bPrimaryAxes);
3164     createAxes(bPrimaryAxes, false);
3165 
3166     pFS->endElement( FSNS( XML_c, XML_radarChart ) );
3167 }
3168 
exportScatterChartSeries(const Reference<chart2::XChartType> & xChartType,const css::uno::Sequence<css::uno::Reference<chart2::XDataSeries>> * pSeries)3169 void ChartExport::exportScatterChartSeries( const Reference< chart2::XChartType >& xChartType,
3170         const css::uno::Sequence<css::uno::Reference<chart2::XDataSeries>>* pSeries)
3171 {
3172     FSHelperPtr pFS = GetFS();
3173     pFS->startElement(FSNS(XML_c, XML_scatterChart));
3174     // TODO:scatterStyle
3175 
3176     sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
3177     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
3178     if( GetProperty( xPropSet, u"SymbolType"_ustr ) )
3179         mAny >>= nSymbolType;
3180 
3181     const char* scatterStyle = "lineMarker";
3182     if (nSymbolType == css::chart::ChartSymbolType::NONE)
3183     {
3184         scatterStyle = "line";
3185     }
3186 
3187     pFS->singleElement(FSNS(XML_c, XML_scatterStyle), XML_val, scatterStyle);
3188 
3189     exportVaryColors(xChartType);
3190     // FIXME: should export xVal and yVal
3191     bool bPrimaryAxes = true;
3192     if (pSeries)
3193         exportSeries_chart(xChartType, *pSeries, bPrimaryAxes);
3194     createAxes(bPrimaryAxes, false);
3195     //exportAxesId(bPrimaryAxes);
3196 
3197     pFS->endElement( FSNS( XML_c, XML_scatterChart ) );
3198 }
3199 
exportScatterChart(const Reference<chart2::XChartType> & xChartType)3200 void ChartExport::exportScatterChart( const Reference< chart2::XChartType >& xChartType )
3201 {
3202     const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
3203     bool bExported = false;
3204     for (const auto& splitDataSeries : aSplitDataSeries)
3205     {
3206         if (!splitDataSeries.hasElements())
3207             continue;
3208 
3209         bExported = true;
3210         exportScatterChartSeries(xChartType, &splitDataSeries);
3211     }
3212     if (!bExported)
3213         exportScatterChartSeries(xChartType, nullptr);
3214 }
3215 
exportStockChart(const Reference<chart2::XChartType> & xChartType)3216 void ChartExport::exportStockChart( const Reference< chart2::XChartType >& xChartType )
3217 {
3218     FSHelperPtr pFS = GetFS();
3219     const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
3220     for (const auto& splitDataSeries : aSplitDataSeries)
3221     {
3222         if (!splitDataSeries.hasElements())
3223             continue;
3224 
3225         pFS->startElement(FSNS(XML_c, XML_stockChart));
3226 
3227         bool bPrimaryAxes = true;
3228         exportCandleStickSeries(splitDataSeries, bPrimaryAxes);
3229 
3230         // export stock properties
3231         Reference< css::chart::XStatisticDisplay > xStockPropProvider(mxDiagram, uno::UNO_QUERY);
3232         if (xStockPropProvider.is())
3233         {
3234             exportHiLowLines();
3235             exportUpDownBars(xChartType);
3236         }
3237 
3238         createAxes(bPrimaryAxes, false);
3239 
3240         pFS->endElement(FSNS(XML_c, XML_stockChart));
3241     }
3242 }
3243 
exportHiLowLines()3244 void ChartExport::exportHiLowLines()
3245 {
3246     FSHelperPtr pFS = GetFS();
3247     // export the chart property
3248     Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
3249 
3250     if (!xChartPropProvider.is())
3251         return;
3252 
3253     Reference< beans::XPropertySet > xStockPropSet = xChartPropProvider->getMinMaxLine();
3254     if( !xStockPropSet.is() )
3255         return;
3256 
3257     pFS->startElement(FSNS(XML_c, XML_hiLowLines));
3258     exportShapeProps( xStockPropSet, false );
3259     pFS->endElement( FSNS( XML_c, XML_hiLowLines ) );
3260 }
3261 
exportUpDownBars(const Reference<chart2::XChartType> & xChartType)3262 void ChartExport::exportUpDownBars( const Reference< chart2::XChartType >& xChartType)
3263 {
3264     if(xChartType->getChartType() != "com.sun.star.chart2.CandleStickChartType")
3265         return;
3266 
3267     FSHelperPtr pFS = GetFS();
3268     // export the chart property
3269     Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
3270     if(!xChartPropProvider.is())
3271         return;
3272 
3273     //  updownbar
3274     pFS->startElement(FSNS(XML_c, XML_upDownBars));
3275     // TODO: gapWidth
3276     pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(150));
3277 
3278     Reference< beans::XPropertySet > xChartPropSet = xChartPropProvider->getUpBar();
3279     if( xChartPropSet.is() )
3280     {
3281         pFS->startElement(FSNS(XML_c, XML_upBars));
3282         // For Linechart with UpDownBars, spPr is not getting imported
3283         // so no need to call the exportShapeProps() for LineChart
3284         if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
3285         {
3286             exportShapeProps(xChartPropSet, false);
3287         }
3288         pFS->endElement( FSNS( XML_c, XML_upBars ) );
3289     }
3290     xChartPropSet = xChartPropProvider->getDownBar();
3291     if( xChartPropSet.is() )
3292     {
3293         pFS->startElement(FSNS(XML_c, XML_downBars));
3294         if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
3295         {
3296             exportShapeProps(xChartPropSet, false);
3297         }
3298         pFS->endElement( FSNS( XML_c, XML_downBars ) );
3299     }
3300     pFS->endElement( FSNS( XML_c, XML_upDownBars ) );
3301 }
3302 
exportSurfaceChart(const Reference<chart2::XChartType> & xChartType)3303 void ChartExport::exportSurfaceChart( const Reference< chart2::XChartType >& xChartType )
3304 {
3305     FSHelperPtr pFS = GetFS();
3306     sal_Int32 nTypeId = XML_surfaceChart;
3307     if( mbIs3DChart )
3308         nTypeId = XML_surface3DChart;
3309     pFS->startElement(FSNS(XML_c, nTypeId));
3310     exportVaryColors(xChartType);
3311     bool bPrimaryAxes = true;
3312     exportAllSeries(xChartType, bPrimaryAxes);
3313     createAxes(bPrimaryAxes, false);
3314 
3315     pFS->endElement( FSNS( XML_c, nTypeId ) );
3316 }
3317 
exportAllSeries(const Reference<chart2::XChartType> & xChartType,bool & rPrimaryAxes)3318 void ChartExport::exportAllSeries(const Reference<chart2::XChartType>& xChartType, bool& rPrimaryAxes)
3319 {
3320     Reference< chart2::XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY );
3321     if( ! xDSCnt.is())
3322         return;
3323 
3324     // export dataseries for current chart-type
3325     Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries());
3326     exportSeries_chart(xChartType, aSeriesSeq, rPrimaryAxes);
3327 }
3328 
exportVaryColors(const Reference<chart2::XChartType> & xChartType)3329 void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& xChartType)
3330 {
3331     FSHelperPtr pFS = GetFS();
3332     try
3333     {
3334         Reference<chart2::XDataSeries> xDataSeries = getPrimaryDataSeries(xChartType);
3335         Reference<beans::XPropertySet> xDataSeriesProps(xDataSeries, uno::UNO_QUERY_THROW);
3336         Any aAnyVaryColors = xDataSeriesProps->getPropertyValue(u"VaryColorsByPoint"_ustr);
3337         bool bVaryColors = false;
3338         aAnyVaryColors >>= bVaryColors;
3339         pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, ToPsz10(bVaryColors));
3340     }
3341     catch (...)
3342     {
3343         pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
3344     }
3345 }
3346 
exportSeries_chart(const Reference<chart2::XChartType> & xChartType,const Sequence<Reference<chart2::XDataSeries>> & rSeriesSeq,bool & rPrimaryAxes)3347 void ChartExport::exportSeries_chart( const Reference<chart2::XChartType>& xChartType,
3348         const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq,
3349         bool& rPrimaryAxes)
3350 {
3351     OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel();
3352     OUString aChartType( xChartType->getChartType());
3353     sal_Int32 eChartType = lcl_getChartType( aChartType );
3354 
3355     for( const auto& rSeries : rSeriesSeq )
3356     {
3357         // export series
3358         Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY );
3359         if( xSource.is())
3360         {
3361             Reference< chart2::XDataSeries > xDataSeries( xSource, uno::UNO_QUERY );
3362             Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
3363                 xSource->getDataSequences());
3364             // search for main sequence and create a series element
3365             {
3366                 sal_Int32 nMainSequenceIndex = -1;
3367                 sal_Int32 nSeriesLength = 0;
3368                 Reference< chart2::data::XDataSequence > xValuesSeq;
3369                 Reference< chart2::data::XDataSequence > xLabelSeq;
3370                 sal_Int32 nSeqIdx=0;
3371                 for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx )
3372                 {
3373                     Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() );
3374                     if( nMainSequenceIndex==-1 )
3375                     {
3376                         Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY );
3377                         OUString aRole;
3378                         if( xSeqProp.is())
3379                             xSeqProp->getPropertyValue(u"Role"_ustr) >>= aRole;
3380                         // "main" sequence
3381                         if( aRole == aLabelRole )
3382                         {
3383                             xValuesSeq.set( xTempValueSeq );
3384                             xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel());
3385                             nMainSequenceIndex = nSeqIdx;
3386                         }
3387                     }
3388                     sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0));
3389                     if( nSeriesLength < nSequenceLength )
3390                         nSeriesLength = nSequenceLength;
3391                 }
3392 
3393                 // have found the main sequence, then xValuesSeq and
3394                 // xLabelSeq contain those.  Otherwise both are empty
3395                 FSHelperPtr pFS = GetFS();
3396 
3397                 pFS->startElement(FSNS(XML_c, XML_ser));
3398 
3399                 // TODO: idx and order
3400                 pFS->singleElement( FSNS( XML_c, XML_idx ),
3401                     XML_val, OString::number(mnSeriesCount) );
3402                 pFS->singleElement( FSNS( XML_c, XML_order ),
3403                     XML_val, OString::number(mnSeriesCount++) );
3404 
3405                 // export label
3406                 if( xLabelSeq.is() )
3407                     exportSeriesText( xLabelSeq, false );
3408 
3409                 Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW);
3410                 if( GetProperty( xPropSet, u"AttachedAxisIndex"_ustr) )
3411                 {
3412                     sal_Int32 nLocalAttachedAxis = 0;
3413                     mAny >>= nLocalAttachedAxis;
3414                     rPrimaryAxes = isPrimaryAxes(nLocalAttachedAxis);
3415                 }
3416 
3417                 // export shape properties
3418                 Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet(
3419                     rSeries, getModel() );
3420                 if( xOldPropSet.is() )
3421                 {
3422                     exportShapeProps( xOldPropSet, false );
3423                 }
3424 
3425                 switch( eChartType )
3426                 {
3427                     case chart::TYPEID_BUBBLE:
3428                     case chart::TYPEID_HORBAR:
3429                     case chart::TYPEID_BAR:
3430                     {
3431                         pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
3432                     }
3433                     break;
3434                     case chart::TYPEID_LINE:
3435                     {
3436                         exportMarker(xOldPropSet);
3437                         break;
3438                     }
3439                     case chart::TYPEID_PIE:
3440                     case chart::TYPEID_DOUGHNUT:
3441                     {
3442                         if( xOldPropSet.is() && GetProperty( xOldPropSet, u"SegmentOffset"_ustr) )
3443                         {
3444                             sal_Int32 nOffset = 0;
3445                             mAny >>= nOffset;
3446                             pFS->singleElement( FSNS( XML_c, XML_explosion ),
3447                                 XML_val, OString::number( nOffset ) );
3448                         }
3449                         break;
3450                     }
3451                     case chart::TYPEID_SCATTER:
3452                     {
3453                         exportMarker(xOldPropSet);
3454                         break;
3455                     }
3456                     case chart::TYPEID_RADARLINE:
3457                     {
3458                         exportMarker(xOldPropSet);
3459                         break;
3460                     }
3461                 }
3462 
3463                 // export data points
3464                 exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType );
3465 
3466                 DataLabelsRange aDLblsRange;
3467                 // export data labels
3468                 exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange, false);
3469 
3470                 exportTrendlines( rSeries );
3471 
3472                 if( eChartType != chart::TYPEID_PIE &&
3473                         eChartType != chart::TYPEID_RADARLINE )
3474                 {
3475                     //export error bars here
3476                     Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY );
3477                     Reference< XPropertySet > xErrorBarYProps;
3478                     xSeriesPropSet->getPropertyValue(u"ErrorBarY"_ustr) >>= xErrorBarYProps;
3479                     if(xErrorBarYProps.is())
3480                         exportErrorBar(xErrorBarYProps, true);
3481                     if (eChartType != chart::TYPEID_BAR &&
3482                             eChartType != chart::TYPEID_HORBAR)
3483                     {
3484                         Reference< XPropertySet > xErrorBarXProps;
3485                         xSeriesPropSet->getPropertyValue(u"ErrorBarX"_ustr) >>= xErrorBarXProps;
3486                         if(xErrorBarXProps.is())
3487                             exportErrorBar(xErrorBarXProps, false);
3488                     }
3489                 }
3490 
3491                 // export categories
3492                 if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() )
3493                     exportSeriesCategory( mxCategoriesValues );
3494 
3495                 if( (eChartType == chart::TYPEID_SCATTER)
3496                     || (eChartType == chart::TYPEID_BUBBLE) )
3497                 {
3498                     // export xVal
3499                     Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-x"_ustr ) );
3500                     if( xSequence.is() )
3501                     {
3502                         Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
3503                         if( xValues.is() )
3504                             exportSeriesValues( xValues, XML_xVal );
3505                     }
3506                     else if( mxCategoriesValues.is() )
3507                         exportSeriesCategory( mxCategoriesValues, XML_xVal );
3508                 }
3509 
3510                 if( eChartType == chart::TYPEID_BUBBLE )
3511                 {
3512                     // export yVal
3513                     Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-y"_ustr ) );
3514                     if( xSequence.is() )
3515                     {
3516                         Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
3517                         if( xValues.is() )
3518                             exportSeriesValues( xValues, XML_yVal );
3519                     }
3520                 }
3521 
3522                 // export values
3523                 if( xValuesSeq.is() )
3524                 {
3525                     sal_Int32 nYValueType = XML_val;
3526                     if( eChartType == chart::TYPEID_SCATTER )
3527                         nYValueType = XML_yVal;
3528                     else if( eChartType == chart::TYPEID_BUBBLE )
3529                         nYValueType = XML_bubbleSize;
3530                     exportSeriesValues( xValuesSeq, nYValueType );
3531                 }
3532 
3533                 if( eChartType == chart::TYPEID_SCATTER
3534                         || eChartType == chart::TYPEID_LINE )
3535                     exportSmooth();
3536 
3537                 // tdf103988: "corrupted" files with Bubble chart opening in MSO
3538                 if( eChartType == chart::TYPEID_BUBBLE )
3539                     pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0");
3540 
3541                 if (!aDLblsRange.empty())
3542                     writeDataLabelsRange(pFS, GetFB(), aDLblsRange);
3543 
3544                 pFS->endElement( FSNS( XML_c, XML_ser ) );
3545             }
3546         }
3547     }
3548 }
3549 
exportSeries_chartex(const Reference<chart2::XChartType> & xChartType,const Sequence<Reference<chart2::XDataSeries>> & rSeriesSeq,const char * sTypeName)3550 void ChartExport::exportSeries_chartex( const Reference<chart2::XChartType>& xChartType,
3551         const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq,
3552         const char* sTypeName)
3553 {
3554     OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel();
3555     OUString aChartType( xChartType->getChartType());
3556     sal_Int32 eChartType = lcl_getChartType( aChartType );
3557 
3558     sal_Int32 nSeriesCnt = 0;
3559     for( const auto& rSeries : rSeriesSeq )
3560     {
3561         // export series
3562         Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY );
3563         if( xSource.is())
3564         {
3565             FSHelperPtr pFS = GetFS();
3566             pFS->startElement(FSNS(XML_cx, XML_series), XML_layoutId, sTypeName);
3567 
3568             Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
3569                 xSource->getDataSequences());
3570 
3571             // search for main sequence and create a series element
3572             sal_Int32 nMainSequenceIndex = -1;
3573             sal_Int32 nSeriesLength = 0;
3574             Reference< chart2::data::XDataSequence > xLabelSeq;
3575             sal_Int32 nSeqIdx=0;
3576             for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx )
3577             {
3578                 Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() );
3579                 if( nMainSequenceIndex==-1 )
3580                 {
3581                     Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY );
3582                     OUString aRole;
3583                     if( xSeqProp.is())
3584                         xSeqProp->getPropertyValue(u"Role"_ustr) >>= aRole;
3585                     // "main" sequence
3586                     if( aRole == aLabelRole )
3587                     {
3588                         xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel());
3589                         nMainSequenceIndex = nSeqIdx;
3590                     }
3591                 }
3592                 sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0));
3593                 if( nSeriesLength < nSequenceLength )
3594                     nSeriesLength = nSequenceLength;
3595             }
3596 
3597             // export label
3598             if( xLabelSeq.is() )
3599                 exportSeriesText( xLabelSeq, true );
3600 
3601             // export shape properties
3602             Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet(
3603                 rSeries, getModel() );
3604             if( xOldPropSet.is() )
3605             {
3606                 exportShapeProps( xOldPropSet, true );
3607             }
3608 
3609             DataLabelsRange aDLblsRange;
3610             // export data labels
3611             exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange, true);
3612 
3613             // dataId links to the correct data set in the <cx:chartData>. See
3614             // DATA_ID_COMMENT
3615             pFS->singleElement(FSNS(XML_cx, XML_dataId), XML_val,
3616                     OString::number(nSeriesCnt++));
3617 
3618             // layoutPr
3619 
3620             // axisId
3621 
3622             // extLst
3623 
3624             pFS->endElement(FSNS(XML_cx, XML_series));
3625         }
3626     }
3627 }
3628 
exportCandleStickSeries(const Sequence<Reference<chart2::XDataSeries>> & aSeriesSeq,bool & rPrimaryAxes)3629 void ChartExport::exportCandleStickSeries(
3630     const Sequence< Reference< chart2::XDataSeries > > & aSeriesSeq,
3631     bool& rPrimaryAxes)
3632 {
3633     for( const Reference< chart2::XDataSeries >& xSeries : aSeriesSeq )
3634     {
3635         rPrimaryAxes = lcl_isSeriesAttachedToFirstAxis(xSeries);
3636 
3637         Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY );
3638         if( xSource.is())
3639         {
3640             // export series in correct order (as we don't store roles)
3641             // with japanese candlesticks: open, low, high, close
3642             // otherwise: low, high, close
3643             Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
3644                 xSource->getDataSequences());
3645 
3646             const char* sSeries[] = {"values-first","values-max","values-min","values-last",nullptr};
3647 
3648             for( sal_Int32 idx = 0; sSeries[idx] != nullptr ; idx++ )
3649             {
3650                 Reference< chart2::data::XLabeledDataSequence > xLabeledSeq( lcl_getDataSequenceByRole( aSeqCnt, OUString::createFromAscii(sSeries[idx]) ) );
3651                 if( xLabeledSeq.is())
3652                 {
3653                     Reference< chart2::data::XDataSequence > xLabelSeq( xLabeledSeq->getLabel());
3654                     Reference< chart2::data::XDataSequence > xValueSeq( xLabeledSeq->getValues());
3655                     {
3656                         FSHelperPtr pFS = GetFS();
3657                         pFS->startElement(FSNS(XML_c, XML_ser));
3658 
3659                         // TODO: idx and order
3660                         // idx attribute should start from 1 and not from 0.
3661                         pFS->singleElement( FSNS( XML_c, XML_idx ),
3662                                 XML_val, OString::number(idx+1) );
3663                         pFS->singleElement( FSNS( XML_c, XML_order ),
3664                                 XML_val, OString::number(idx+1) );
3665 
3666                         // export label
3667                         if( xLabelSeq.is() )
3668                             exportSeriesText( xLabelSeq, false );
3669 
3670                         // TODO:export shape properties
3671 
3672                         // export categories
3673                         if( mxCategoriesValues.is() )
3674                             exportSeriesCategory( mxCategoriesValues );
3675 
3676                         // export values
3677                         if( xValueSeq.is() )
3678                             exportSeriesValues( xValueSeq );
3679 
3680                         pFS->endElement( FSNS( XML_c, XML_ser ) );
3681                     }
3682                 }
3683             }
3684         }
3685     }
3686 }
3687 
exportSeriesText(const Reference<chart2::data::XDataSequence> & xValueSeq,bool bIsChartex)3688 void ChartExport::exportSeriesText( const Reference< chart2::data::XDataSequence > & xValueSeq,
3689         bool bIsChartex)
3690 {
3691     FSHelperPtr pFS = GetFS();
3692 
3693     OUString aLabelString = lcl_flattenStringSequence(lcl_getLabelSequence(xValueSeq));
3694 
3695     if (bIsChartex) {
3696         lcl_writeChartexString(pFS, aLabelString);
3697     } else {
3698         pFS->startElement(FSNS(XML_c, XML_tx));
3699 
3700         OUString aCellRange =  xValueSeq->getSourceRangeRepresentation();
3701         aCellRange = parseFormula( aCellRange );
3702         pFS->startElement(FSNS(XML_c, XML_strRef));
3703 
3704         pFS->startElement(FSNS(XML_c, XML_f));
3705         pFS->writeEscaped( aCellRange );
3706         pFS->endElement( FSNS( XML_c, XML_f ) );
3707 
3708         pFS->startElement(FSNS(XML_c, XML_strCache));
3709         pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, "1");
3710         pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, "0");
3711         pFS->startElement(FSNS(XML_c, XML_v));
3712         pFS->writeEscaped( aLabelString );
3713         pFS->endElement( FSNS( XML_c, XML_v ) );
3714         pFS->endElement( FSNS( XML_c, XML_pt ) );
3715         pFS->endElement( FSNS( XML_c, XML_strCache ) );
3716         pFS->endElement( FSNS( XML_c, XML_strRef ) );
3717         pFS->endElement( FSNS( XML_c, XML_tx ) );
3718     }
3719 }
3720 
exportSeriesCategory(const Reference<chart2::data::XDataSequence> & xValueSeq,sal_Int32 nValueType)3721 void ChartExport::exportSeriesCategory( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType )
3722 {
3723     FSHelperPtr pFS = GetFS();
3724     pFS->startElement(FSNS(XML_c, nValueType));
3725 
3726     OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
3727     const Sequence< Sequence< OUString >> aFinalSplitSource = (nValueType == XML_cat) ? getSplitCategoriesList(aCellRange) : Sequence< Sequence< OUString>>(0);
3728     aCellRange = parseFormula( aCellRange );
3729 
3730     if(aFinalSplitSource.getLength() > 1)
3731     {
3732         // export multi level category axis labels
3733         pFS->startElement(FSNS(XML_c, XML_multiLvlStrRef));
3734 
3735         pFS->startElement(FSNS(XML_c, XML_f));
3736         pFS->writeEscaped(aCellRange);
3737         pFS->endElement(FSNS(XML_c, XML_f));
3738 
3739         pFS->startElement(FSNS(XML_c, XML_multiLvlStrCache));
3740         pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(aFinalSplitSource[0].getLength()));
3741         for(const auto& rSeq : aFinalSplitSource)
3742         {
3743             pFS->startElement(FSNS(XML_c, XML_lvl));
3744             for(sal_Int32 j = 0; j < rSeq.getLength(); j++)
3745             {
3746                 if(!rSeq[j].isEmpty())
3747                 {
3748                     pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(j));
3749                     pFS->startElement(FSNS(XML_c, XML_v));
3750                     pFS->writeEscaped(rSeq[j]);
3751                     pFS->endElement(FSNS(XML_c, XML_v));
3752                     pFS->endElement(FSNS(XML_c, XML_pt));
3753                 }
3754             }
3755             pFS->endElement(FSNS(XML_c, XML_lvl));
3756         }
3757 
3758         pFS->endElement(FSNS(XML_c, XML_multiLvlStrCache));
3759         pFS->endElement(FSNS(XML_c, XML_multiLvlStrRef));
3760     }
3761     else
3762     {
3763         // export single category axis labels
3764         bool bWriteDateCategories = mbHasDateCategories && (nValueType == XML_cat);
3765         OUString aNumberFormatString;
3766         if (bWriteDateCategories)
3767         {
3768             Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
3769             if( xAxisXSupp.is())
3770             {
3771                 Reference< XPropertySet > xAxisProp = xAxisXSupp->getXAxis();
3772                 if (GetProperty(xAxisProp, u"NumberFormat"_ustr))
3773                 {
3774                     sal_Int32 nKey = 0;
3775                     mAny >>= nKey;
3776                     aNumberFormatString = getNumberFormatCode(nKey);
3777                 }
3778             }
3779             if (aNumberFormatString.isEmpty())
3780                 bWriteDateCategories = false;
3781         }
3782 
3783         pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef));
3784 
3785         pFS->startElement(FSNS(XML_c, XML_f));
3786         pFS->writeEscaped(aCellRange);
3787         pFS->endElement(FSNS(XML_c, XML_f));
3788 
3789         pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache));
3790         if (bWriteDateCategories)
3791         {
3792             pFS->startElement(FSNS(XML_c, XML_formatCode));
3793             pFS->writeEscaped(aNumberFormatString);
3794             pFS->endElement(FSNS(XML_c, XML_formatCode));
3795 
3796             std::vector<double> aDateCategories = lcl_getAllValuesFromSequence(xValueSeq);
3797             const sal_Int32 ptCount = aDateCategories.size();
3798             pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
3799             for (sal_Int32 i = 0; i < ptCount; i++)
3800             {
3801                 if (!std::isnan(aDateCategories[i]))
3802                 {
3803                     pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
3804                     pFS->startElement(FSNS(XML_c, XML_v));
3805                     pFS->write(OString::number(aDateCategories[i]));
3806                     pFS->endElement(FSNS(XML_c, XML_v));
3807                     pFS->endElement(FSNS(XML_c, XML_pt));
3808                 }
3809             }
3810         }
3811         else
3812         {
3813             std::vector<OUString> aCategories;
3814             lcl_fillCategoriesIntoStringVector(xValueSeq, aCategories);
3815             const sal_Int32 ptCount = aCategories.size();
3816             pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
3817             for (sal_Int32 i = 0; i < ptCount; i++)
3818             {
3819                 pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
3820                 pFS->startElement(FSNS(XML_c, XML_v));
3821                 pFS->writeEscaped(aCategories[i]);
3822                 pFS->endElement(FSNS(XML_c, XML_v));
3823                 pFS->endElement(FSNS(XML_c, XML_pt));
3824             }
3825         }
3826 
3827         pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache));
3828         pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef));
3829     }
3830 
3831     pFS->endElement( FSNS( XML_c, nValueType ) );
3832 }
3833 
exportSeriesValues(const Reference<chart2::data::XDataSequence> & xValueSeq,sal_Int32 nValueType)3834 void ChartExport::exportSeriesValues( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType )
3835 {
3836     FSHelperPtr pFS = GetFS();
3837     pFS->startElement(FSNS(XML_c, nValueType));
3838 
3839     OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
3840     aCellRange = parseFormula( aCellRange );
3841     // TODO: need to handle XML_multiLvlStrRef according to aCellRange
3842     pFS->startElement(FSNS(XML_c, XML_numRef));
3843 
3844     pFS->startElement(FSNS(XML_c, XML_f));
3845     pFS->writeEscaped( aCellRange );
3846     pFS->endElement( FSNS( XML_c, XML_f ) );
3847 
3848     ::std::vector< double > aValues = lcl_getAllValuesFromSequence( xValueSeq );
3849     sal_Int32 ptCount = aValues.size();
3850     pFS->startElement(FSNS(XML_c, XML_numCache));
3851     pFS->startElement(FSNS(XML_c, XML_formatCode));
3852     OUString sNumberFormatString(u"General"_ustr);
3853     const sal_Int32 nKey = xValueSeq.is() ? xValueSeq->getNumberFormatKeyByIndex(-1) : 0;
3854     if (nKey > 0)
3855         sNumberFormatString = getNumberFormatCode(nKey);
3856     pFS->writeEscaped(sNumberFormatString);
3857     pFS->endElement( FSNS( XML_c, XML_formatCode ) );
3858     pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
3859 
3860     for( sal_Int32 i = 0; i < ptCount; i++ )
3861     {
3862         if (!std::isnan(aValues[i]))
3863         {
3864             pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
3865             pFS->startElement(FSNS(XML_c, XML_v));
3866             pFS->write(aValues[i]);
3867             pFS->endElement(FSNS(XML_c, XML_v));
3868             pFS->endElement(FSNS(XML_c, XML_pt));
3869         }
3870     }
3871 
3872     pFS->endElement( FSNS( XML_c, XML_numCache ) );
3873     pFS->endElement( FSNS( XML_c, XML_numRef ) );
3874     pFS->endElement( FSNS( XML_c, nValueType ) );
3875 }
3876 
exportShapeProps(const Reference<XPropertySet> & xPropSet,bool bIsChartex)3877 void ChartExport::exportShapeProps( const Reference< XPropertySet >& xPropSet,
3878         bool bIsChartex)
3879 {
3880     sal_Int32 nChartNS = bIsChartex ? XML_cx : XML_c;
3881     FSHelperPtr pFS = GetFS();
3882     pFS->startElement(FSNS(nChartNS, XML_spPr));
3883 
3884     exportFill( xPropSet );
3885     WriteOutline( xPropSet, getModel() );
3886 
3887     pFS->endElement( FSNS( nChartNS, XML_spPr ) );
3888 }
3889 
exportTextProps(const Reference<XPropertySet> & xPropSet,bool bIsChartex)3890 void ChartExport::exportTextProps(const Reference<XPropertySet>& xPropSet,
3891         bool bIsChartex)
3892 {
3893     FSHelperPtr pFS = GetFS();
3894 
3895     const sal_Int32 nChartNS = bIsChartex ? XML_cx : XML_c;
3896     pFS->startElement(FSNS(nChartNS, XML_txPr));
3897 
3898     sal_Int32 nRotation = 0;
3899     const char* textWordWrap = nullptr;
3900 
3901     if (auto xServiceInfo = uno::Reference<lang::XServiceInfo>(xPropSet, uno::UNO_QUERY))
3902     {
3903         double fMultiplier = 0.0;
3904         // We have at least two possible units of returned value: degrees (e.g., for data labels),
3905         // and 100ths of degree (e.g., for axes labels). The latter is returned as an Any wrapping
3906         // a sal_Int32 value (see WrappedTextRotationProperty::convertInnerToOuterValue), while
3907         // the former is double. So we could test the contained type to decide which multiplier to
3908         // use. But testing the service info should be more robust.
3909         if (xServiceInfo->supportsService(u"com.sun.star.chart.ChartAxis"_ustr))
3910             fMultiplier = -600.0;
3911         else if (xServiceInfo->supportsService(u"com.sun.star.chart2.DataSeries"_ustr) || xServiceInfo->supportsService(u"com.sun.star.chart2.DataPointProperties"_ustr))
3912         {
3913             fMultiplier = -60000.0;
3914             bool bTextWordWrap = false;
3915             if ((xPropSet->getPropertyValue(u"TextWordWrap"_ustr) >>= bTextWordWrap) && bTextWordWrap)
3916                 textWordWrap = "square";
3917             else
3918                 textWordWrap = "none";
3919         }
3920 
3921         if (fMultiplier)
3922         {
3923             double fTextRotation = 0.0;
3924             uno::Any aAny = xPropSet->getPropertyValue(u"TextRotation"_ustr);
3925             if (aAny.hasValue() && (aAny >>= fTextRotation))
3926             {
3927                 fTextRotation *= fMultiplier;
3928                 // The MS Office UI allows values only in range of [-90,90].
3929                 if (fTextRotation < -5400000.0 && fTextRotation > -16200000.0)
3930                 {
3931                     // Reflect the angle if the value is between 90° and 270°
3932                     fTextRotation += 10800000.0;
3933                 }
3934                 else if (fTextRotation <= -16200000.0)
3935                 {
3936                     fTextRotation += 21600000.0;
3937                 }
3938                 nRotation = std::round(fTextRotation);
3939             }
3940         }
3941     }
3942 
3943     if (nRotation)
3944         pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_rot, OString::number(nRotation), XML_wrap, textWordWrap);
3945     else
3946         pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_wrap, textWordWrap);
3947 
3948     pFS->singleElement(FSNS(XML_a, XML_lstStyle));
3949 
3950     pFS->startElement(FSNS(XML_a, XML_p));
3951     pFS->startElement(FSNS(XML_a, XML_pPr));
3952 
3953     WriteRunProperties(xPropSet, false, XML_defRPr, true, o3tl::temporary(false),
3954                        o3tl::temporary(sal_Int32()));
3955 
3956     pFS->endElement(FSNS(XML_a, XML_pPr));
3957     pFS->endElement(FSNS(XML_a, XML_p));
3958     pFS->endElement(FSNS(nChartNS, XML_txPr));
3959 }
3960 
InitPlotArea()3961 void ChartExport::InitPlotArea( )
3962 {
3963     Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
3964 
3965     //    Check for supported services and then the properties provided by this service.
3966     Reference<lang::XServiceInfo> xServiceInfo (mxDiagram, uno::UNO_QUERY);
3967     if (xServiceInfo.is())
3968     {
3969         if (xServiceInfo->supportsService(u"com.sun.star.chart.ChartAxisZSupplier"_ustr))
3970         {
3971             xDiagramProperties->getPropertyValue(u"HasZAxis"_ustr) >>= mbHasZAxis;
3972         }
3973     }
3974 
3975     xDiagramProperties->getPropertyValue(u"Dim3D"_ustr) >>=  mbIs3DChart;
3976 
3977     if( mbHasCategoryLabels && mxNewDiagram.is())
3978     {
3979         Reference< chart2::data::XLabeledDataSequence > xCategories(
3980                 lcl_getCategories( mxNewDiagram, &mbHasDateCategories ) );
3981         if( xCategories.is() )
3982         {
3983             mxCategoriesValues.set( xCategories->getValues() );
3984         }
3985     }
3986 }
3987 
exportAxes(bool bIsChartex)3988 void ChartExport::exportAxes( bool bIsChartex )
3989 {
3990     sal_Int32 nSize = maAxes.size();
3991     // let's export the axis types in the right order
3992     for ( sal_Int32 nSortIdx = AXIS_PRIMARY_X; nSortIdx <= AXIS_SECONDARY_Y; nSortIdx++ )
3993     {
3994         for ( sal_Int32 nIdx = 0; nIdx < nSize; nIdx++ )
3995         {
3996             if (nSortIdx == maAxes[nIdx].nAxisType)
3997                 exportAxis( maAxes[nIdx], bIsChartex );
3998         }
3999     }
4000 }
4001 
4002 namespace {
4003 
getXAxisTypeByChartType(sal_Int32 eChartType)4004 sal_Int32 getXAxisTypeByChartType(sal_Int32 eChartType)
4005 {
4006     if( (eChartType == chart::TYPEID_SCATTER)
4007             || (eChartType == chart::TYPEID_BUBBLE) )
4008         return  XML_valAx;
4009     else if( eChartType == chart::TYPEID_STOCK )
4010         return  XML_dateAx;
4011 
4012     return XML_catAx;
4013 }
4014 
getRealXAxisType(sal_Int32 nAxisType)4015 sal_Int32 getRealXAxisType(sal_Int32 nAxisType)
4016 {
4017     if( nAxisType == chart2::AxisType::CATEGORY )
4018         return XML_catAx;
4019     else if( nAxisType == chart2::AxisType::DATE )
4020         return XML_dateAx;
4021     else if( nAxisType == chart2::AxisType::SERIES )
4022         return XML_serAx;
4023 
4024     return XML_valAx;
4025 }
4026 
4027 }
4028 
exportAxis(const AxisIdPair & rAxisIdPair,bool bIsChartex)4029 void ChartExport::exportAxis(const AxisIdPair& rAxisIdPair, bool bIsChartex)
4030 {
4031     // get some properties from document first
4032     bool bHasXAxisTitle = false,
4033          bHasYAxisTitle = false,
4034          bHasZAxisTitle = false,
4035          bHasSecondaryXAxisTitle = false,
4036          bHasSecondaryYAxisTitle = false;
4037     bool bHasXAxisMajorGrid = false,
4038          bHasXAxisMinorGrid = false,
4039          bHasYAxisMajorGrid = false,
4040          bHasYAxisMinorGrid = false,
4041          bHasZAxisMajorGrid = false,
4042          bHasZAxisMinorGrid = false;
4043 
4044     Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
4045 
4046     xDiagramProperties->getPropertyValue(u"HasXAxisTitle"_ustr) >>= bHasXAxisTitle;
4047     xDiagramProperties->getPropertyValue(u"HasYAxisTitle"_ustr) >>= bHasYAxisTitle;
4048     xDiagramProperties->getPropertyValue(u"HasZAxisTitle"_ustr) >>= bHasZAxisTitle;
4049     xDiagramProperties->getPropertyValue(u"HasSecondaryXAxisTitle"_ustr) >>=  bHasSecondaryXAxisTitle;
4050     xDiagramProperties->getPropertyValue(u"HasSecondaryYAxisTitle"_ustr) >>=  bHasSecondaryYAxisTitle;
4051 
4052     xDiagramProperties->getPropertyValue(u"HasXAxisGrid"_ustr) >>=  bHasXAxisMajorGrid;
4053     xDiagramProperties->getPropertyValue(u"HasYAxisGrid"_ustr) >>=  bHasYAxisMajorGrid;
4054     xDiagramProperties->getPropertyValue(u"HasZAxisGrid"_ustr) >>=  bHasZAxisMajorGrid;
4055 
4056     xDiagramProperties->getPropertyValue(u"HasXAxisHelpGrid"_ustr) >>=  bHasXAxisMinorGrid;
4057     xDiagramProperties->getPropertyValue(u"HasYAxisHelpGrid"_ustr) >>=  bHasYAxisMinorGrid;
4058     xDiagramProperties->getPropertyValue(u"HasZAxisHelpGrid"_ustr) >>=  bHasZAxisMinorGrid;
4059 
4060     Reference< XPropertySet > xAxisProp;
4061     Reference< drawing::XShape > xAxisTitle;
4062     Reference< beans::XPropertySet > xMajorGrid;
4063     Reference< beans::XPropertySet > xMinorGrid;
4064     sal_Int32 nAxisType = XML_catAx;
4065     const char* sAxPos = nullptr;
4066 
4067     switch( rAxisIdPair.nAxisType )
4068     {
4069         case AXIS_PRIMARY_X:
4070         {
4071             Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
4072             if( xAxisXSupp.is())
4073                 xAxisProp = xAxisXSupp->getXAxis();
4074             if( bHasXAxisTitle )
4075                 xAxisTitle = xAxisXSupp->getXAxisTitle();
4076             if( bHasXAxisMajorGrid )
4077                 xMajorGrid = xAxisXSupp->getXMainGrid();
4078             if( bHasXAxisMinorGrid )
4079                 xMinorGrid = xAxisXSupp->getXHelpGrid();
4080 
4081             nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 0);
4082             if( nAxisType != -1 )
4083                 nAxisType = getRealXAxisType(nAxisType);
4084             else
4085                 nAxisType = getXAxisTypeByChartType( getChartType() );
4086             // FIXME: axPos, need to check axis direction
4087             sAxPos = "b";
4088             break;
4089         }
4090         case AXIS_PRIMARY_Y:
4091         {
4092             Reference< css::chart::XAxisYSupplier > xAxisYSupp( mxDiagram, uno::UNO_QUERY );
4093             if( xAxisYSupp.is())
4094                 xAxisProp = xAxisYSupp->getYAxis();
4095             if( bHasYAxisTitle )
4096                 xAxisTitle = xAxisYSupp->getYAxisTitle();
4097             if( bHasYAxisMajorGrid )
4098                 xMajorGrid = xAxisYSupp->getYMainGrid();
4099             if( bHasYAxisMinorGrid )
4100                 xMinorGrid = xAxisYSupp->getYHelpGrid();
4101 
4102             nAxisType = XML_valAx;
4103             // FIXME: axPos, need to check axis direction
4104             sAxPos = "l";
4105             break;
4106         }
4107         case AXIS_PRIMARY_Z:
4108         {
4109             Reference< css::chart::XAxisZSupplier > xAxisZSupp( mxDiagram, uno::UNO_QUERY );
4110             if( xAxisZSupp.is())
4111                 xAxisProp = xAxisZSupp->getZAxis();
4112             if( bHasZAxisTitle )
4113                 xAxisTitle = xAxisZSupp->getZAxisTitle();
4114             if( bHasZAxisMajorGrid )
4115                 xMajorGrid = xAxisZSupp->getZMainGrid();
4116             if( bHasZAxisMinorGrid )
4117                 xMinorGrid = xAxisZSupp->getZHelpGrid();
4118 
4119             sal_Int32 eChartType = getChartType( );
4120             if( (eChartType == chart::TYPEID_SCATTER)
4121                 || (eChartType == chart::TYPEID_BUBBLE) )
4122                 nAxisType = XML_valAx;
4123             else if( eChartType == chart::TYPEID_STOCK )
4124                 nAxisType = XML_dateAx;
4125             else if( eChartType == chart::TYPEID_BAR || eChartType == chart::TYPEID_AREA )
4126                 nAxisType = XML_serAx;
4127             // FIXME: axPos, need to check axis direction
4128             sAxPos = "b";
4129             break;
4130         }
4131         case AXIS_SECONDARY_X:
4132         {
4133             Reference< css::chart::XTwoAxisXSupplier > xAxisTwoXSupp( mxDiagram, uno::UNO_QUERY );
4134             if( xAxisTwoXSupp.is())
4135                 xAxisProp = xAxisTwoXSupp->getSecondaryXAxis();
4136             if( bHasSecondaryXAxisTitle )
4137             {
4138                 Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
4139                 xAxisTitle = xAxisSupp->getSecondXAxisTitle();
4140             }
4141 
4142             nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 1);
4143             if( nAxisType != -1 )
4144                 nAxisType = getRealXAxisType(nAxisType);
4145             else
4146                 nAxisType = getXAxisTypeByChartType( getChartType() );
4147             // FIXME: axPos, need to check axis direction
4148             sAxPos = "t";
4149             break;
4150         }
4151         case AXIS_SECONDARY_Y:
4152         {
4153             Reference< css::chart::XTwoAxisYSupplier > xAxisTwoYSupp( mxDiagram, uno::UNO_QUERY );
4154             if( xAxisTwoYSupp.is())
4155                 xAxisProp = xAxisTwoYSupp->getSecondaryYAxis();
4156             if( bHasSecondaryYAxisTitle )
4157             {
4158                 Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
4159                 xAxisTitle = xAxisSupp->getSecondYAxisTitle();
4160             }
4161 
4162             nAxisType = XML_valAx;
4163             // FIXME: axPos, need to check axis direction
4164             sAxPos = "r";
4165             break;
4166         }
4167     }
4168 
4169     if (bIsChartex) {
4170         exportOneAxis_chartex(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType,
4171                 rAxisIdPair);
4172     } else {
4173         exportOneAxis_chart(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType,
4174                 sAxPos, rAxisIdPair);
4175     }
4176 }
4177 
getTickMarkLocStr(sal_Int32 nValue)4178 static const char *getTickMarkLocStr(sal_Int32 nValue)
4179 {
4180     const bool bInner = nValue & css::chart::ChartAxisMarks::INNER;
4181     const bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER;
4182     if( bInner && bOuter ) {
4183         return "cross";
4184     } else if( bInner ) {
4185         return "in";
4186     } else if( bOuter ) {
4187         return "out";
4188     } else {
4189         return "none";
4190     }
4191 }
4192 
exportOneAxis_chart(const Reference<XPropertySet> & xAxisProp,const Reference<drawing::XShape> & xAxisTitle,const Reference<XPropertySet> & xMajorGrid,const Reference<XPropertySet> & xMinorGrid,sal_Int32 nAxisType,const char * sAxisPos,const AxisIdPair & rAxisIdPair)4193 void ChartExport::exportOneAxis_chart(
4194     const Reference< XPropertySet >& xAxisProp,
4195     const Reference< drawing::XShape >& xAxisTitle,
4196     const Reference< XPropertySet >& xMajorGrid,
4197     const Reference< XPropertySet >& xMinorGrid,
4198     sal_Int32 nAxisType,
4199     const char* sAxisPos,
4200     const AxisIdPair& rAxisIdPair)
4201 {
4202     FSHelperPtr pFS = GetFS();
4203     pFS->startElement(FSNS(XML_c, nAxisType));
4204     pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(rAxisIdPair.nAxisId));
4205 
4206     pFS->startElement(FSNS(XML_c, XML_scaling));
4207 
4208     // logBase, min, max
4209     if(GetProperty( xAxisProp, u"Logarithmic"_ustr ) )
4210     {
4211         bool bLogarithmic = false;
4212         mAny >>= bLogarithmic;
4213         if( bLogarithmic )
4214         {
4215             // default value is 10?
4216             pFS->singleElement(FSNS(XML_c, XML_logBase), XML_val, OString::number(10));
4217         }
4218     }
4219 
4220     // orientation: minMax, maxMin
4221     bool bReverseDirection = false;
4222     if(GetProperty( xAxisProp, u"ReverseDirection"_ustr ) )
4223         mAny >>= bReverseDirection;
4224 
4225     const char* orientation = bReverseDirection ? "maxMin":"minMax";
4226     pFS->singleElement(FSNS(XML_c, XML_orientation), XML_val, orientation);
4227 
4228     bool bAutoMax = false;
4229     if(GetProperty( xAxisProp, u"AutoMax"_ustr ) )
4230         mAny >>= bAutoMax;
4231 
4232     if( !bAutoMax && (GetProperty( xAxisProp, u"Max"_ustr ) ) )
4233     {
4234         double dMax = 0;
4235         mAny >>= dMax;
4236         pFS->singleElement(FSNS(XML_c, XML_max), XML_val, OString::number(dMax));
4237     }
4238 
4239     bool bAutoMin = false;
4240     if(GetProperty( xAxisProp, u"AutoMin"_ustr ) )
4241         mAny >>= bAutoMin;
4242 
4243     if( !bAutoMin && (GetProperty( xAxisProp, u"Min"_ustr ) ) )
4244     {
4245         double dMin = 0;
4246         mAny >>= dMin;
4247         pFS->singleElement(FSNS(XML_c, XML_min), XML_val, OString::number(dMin));
4248     }
4249 
4250     pFS->endElement( FSNS( XML_c, XML_scaling ) );
4251 
4252     bool bVisible = true;
4253     if( xAxisProp.is() )
4254     {
4255         xAxisProp->getPropertyValue(u"Visible"_ustr) >>=  bVisible;
4256     }
4257 
4258     // only export each axis only once non-deleted
4259     auto aItInsertedPair = maExportedAxis.insert(rAxisIdPair.nAxisType);
4260     bool bDeleted = !aItInsertedPair.second;
4261 
4262     pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, !bDeleted && bVisible ? "0" : "1");
4263 
4264     // FIXME: axPos, need to check the property "ReverseDirection"
4265     pFS->singleElement(FSNS(XML_c, XML_axPos), XML_val, sAxisPos);
4266     // major grid line
4267     if( xMajorGrid.is())
4268     {
4269         pFS->startElement(FSNS(XML_c, XML_majorGridlines));
4270         exportShapeProps( xMajorGrid, false );
4271         pFS->endElement( FSNS( XML_c, XML_majorGridlines ) );
4272     }
4273 
4274     // minor grid line
4275     if( xMinorGrid.is())
4276     {
4277         pFS->startElement(FSNS(XML_c, XML_minorGridlines));
4278         exportShapeProps( xMinorGrid, false );
4279         pFS->endElement( FSNS( XML_c, XML_minorGridlines ) );
4280     }
4281 
4282     // title
4283     if( xAxisTitle.is() )
4284         exportTitle( xAxisTitle, false );
4285 
4286     bool bLinkedNumFmt = true;
4287     if (GetProperty(xAxisProp, u"LinkNumberFormatToSource"_ustr))
4288         mAny >>= bLinkedNumFmt;
4289 
4290     OUString aNumberFormatString(u"General"_ustr);
4291     if (GetProperty(xAxisProp, u"NumberFormat"_ustr))
4292     {
4293         sal_Int32 nKey = 0;
4294         mAny >>= nKey;
4295         aNumberFormatString = getNumberFormatCode(nKey);
4296     }
4297 
4298     pFS->singleElement(FSNS(XML_c, XML_numFmt),
4299             XML_formatCode, aNumberFormatString,
4300             XML_sourceLinked, bLinkedNumFmt ? "1" : "0");
4301 
4302     // majorTickMark
4303     sal_Int32 nValue = 0;
4304     if(GetProperty( xAxisProp, u"Marks"_ustr ) )
4305     {
4306         mAny >>= nValue;
4307         pFS->singleElement(FSNS(XML_c, XML_majorTickMark), XML_val,
4308                 getTickMarkLocStr(nValue));
4309     }
4310     // minorTickMark
4311     if(GetProperty( xAxisProp, u"HelpMarks"_ustr ) )
4312     {
4313         mAny >>= nValue;
4314         pFS->singleElement(FSNS(XML_c, XML_minorTickMark), XML_val,
4315                 getTickMarkLocStr(nValue));
4316     }
4317     // tickLblPos
4318     const char* sTickLblPos = nullptr;
4319     bool bDisplayLabel = true;
4320     if(GetProperty( xAxisProp, u"DisplayLabels"_ustr ) )
4321         mAny >>= bDisplayLabel;
4322     if( bDisplayLabel && (GetProperty( xAxisProp, u"LabelPosition"_ustr ) ) )
4323     {
4324         css::chart::ChartAxisLabelPosition eLabelPosition = css::chart::ChartAxisLabelPosition_NEAR_AXIS;
4325         mAny >>= eLabelPosition;
4326         switch( eLabelPosition )
4327         {
4328             case css::chart::ChartAxisLabelPosition_NEAR_AXIS:
4329             case css::chart::ChartAxisLabelPosition_NEAR_AXIS_OTHER_SIDE:
4330                 sTickLblPos = "nextTo";
4331                 break;
4332             case css::chart::ChartAxisLabelPosition_OUTSIDE_START:
4333                 sTickLblPos = "low";
4334                 break;
4335             case css::chart::ChartAxisLabelPosition_OUTSIDE_END:
4336                 sTickLblPos = "high";
4337                 break;
4338             default:
4339                 sTickLblPos = "nextTo";
4340                 break;
4341         }
4342     }
4343     else
4344     {
4345         sTickLblPos = "none";
4346     }
4347     pFS->singleElement(FSNS(XML_c, XML_tickLblPos), XML_val, sTickLblPos);
4348 
4349     // shape properties
4350     exportShapeProps( xAxisProp, false );
4351 
4352     exportTextProps(xAxisProp, false);
4353 
4354     pFS->singleElement(FSNS(XML_c, XML_crossAx), XML_val, OString::number(rAxisIdPair.nCrossAx));
4355 
4356     // crosses & crossesAt
4357     bool bCrossesValue = false;
4358     const char* sCrosses = nullptr;
4359     // do not export the CrossoverPosition/CrossoverValue, if the axis is deleted and not visible
4360     if( GetProperty( xAxisProp, u"CrossoverPosition"_ustr ) && !bDeleted && bVisible )
4361     {
4362         css::chart::ChartAxisPosition ePosition( css::chart::ChartAxisPosition_ZERO );
4363         mAny >>= ePosition;
4364         switch( ePosition )
4365         {
4366             case css::chart::ChartAxisPosition_START:
4367                 sCrosses = "min";
4368                 break;
4369             case css::chart::ChartAxisPosition_END:
4370                 sCrosses = "max";
4371                 break;
4372             case css::chart::ChartAxisPosition_ZERO:
4373                 sCrosses = "autoZero";
4374                 break;
4375             default:
4376                 bCrossesValue = true;
4377                 break;
4378         }
4379     }
4380 
4381     if( bCrossesValue && GetProperty( xAxisProp, u"CrossoverValue"_ustr ) )
4382     {
4383         double dValue = 0;
4384         mAny >>= dValue;
4385         pFS->singleElement(FSNS(XML_c, XML_crossesAt), XML_val, OString::number(dValue));
4386     }
4387     else
4388     {
4389         if(sCrosses)
4390         {
4391             pFS->singleElement(FSNS(XML_c, XML_crosses), XML_val, sCrosses);
4392         }
4393     }
4394 
4395     if( ( nAxisType == XML_catAx )
4396         || ( nAxisType == XML_dateAx ) )
4397     {
4398         // FIXME: seems not support? use default value,
4399         const char* const isAuto = "1";
4400         pFS->singleElement(FSNS(XML_c, XML_auto), XML_val, isAuto);
4401 
4402         if( nAxisType == XML_catAx )
4403         {
4404             // FIXME: seems not support? lblAlgn
4405             const char* const sLblAlgn = "ctr";
4406             pFS->singleElement(FSNS(XML_c, XML_lblAlgn), XML_val, sLblAlgn);
4407         }
4408 
4409         // FIXME: seems not support? lblOffset
4410         pFS->singleElement(FSNS(XML_c, XML_lblOffset), XML_val, OString::number(100));
4411 
4412         // export baseTimeUnit, majorTimeUnit, minorTimeUnit of Date axis
4413         if( nAxisType == XML_dateAx )
4414         {
4415             sal_Int32 nAxisIndex = -1;
4416             if( rAxisIdPair.nAxisType == AXIS_PRIMARY_X )
4417                 nAxisIndex = 0;
4418             else if( rAxisIdPair.nAxisType == AXIS_SECONDARY_X )
4419                 nAxisIndex = 1;
4420 
4421             cssc::TimeIncrement aTimeIncrement = lcl_getDateTimeIncrement( mxNewDiagram, nAxisIndex );
4422             sal_Int32 nTimeResolution = css::chart::TimeUnit::DAY;
4423             if( aTimeIncrement.TimeResolution >>= nTimeResolution )
4424                 pFS->singleElement(FSNS(XML_c, XML_baseTimeUnit), XML_val, lclGetTimeUnitToken(nTimeResolution));
4425 
4426             cssc::TimeInterval aInterval;
4427             if( aTimeIncrement.MajorTimeInterval >>= aInterval )
4428             {
4429                 pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(aInterval.Number));
4430                 pFS->singleElement(FSNS(XML_c, XML_majorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit));
4431             }
4432             if( aTimeIncrement.MinorTimeInterval >>= aInterval )
4433             {
4434                 pFS->singleElement(FSNS(XML_c, XML_minorUnit), XML_val, OString::number(aInterval.Number));
4435                 pFS->singleElement(FSNS(XML_c, XML_minorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit));
4436             }
4437         }
4438 
4439         // FIXME: seems not support? noMultiLvlLbl
4440         pFS->singleElement(FSNS(XML_c, XML_noMultiLvlLbl), XML_val, OString::number(0));
4441     }
4442 
4443     // crossBetween
4444     if( nAxisType == XML_valAx )
4445     {
4446         if( lcl_isCategoryAxisShifted( mxNewDiagram ))
4447             pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "between");
4448         else
4449             pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "midCat");
4450     }
4451 
4452     // majorUnit
4453     bool bAutoStepMain = false;
4454     if(GetProperty( xAxisProp, u"AutoStepMain"_ustr ) )
4455         mAny >>= bAutoStepMain;
4456 
4457     if( !bAutoStepMain && (GetProperty( xAxisProp, u"StepMain"_ustr ) ) )
4458     {
4459         double dMajorUnit = 0;
4460         mAny >>= dMajorUnit;
4461         pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(dMajorUnit));
4462     }
4463     // minorUnit
4464     bool bAutoStepHelp = false;
4465     if(GetProperty( xAxisProp, u"AutoStepHelp"_ustr ) )
4466         mAny >>= bAutoStepHelp;
4467 
4468     if( !bAutoStepHelp && (GetProperty( xAxisProp, u"StepHelp"_ustr ) ) )
4469     {
4470         double dMinorUnit = 0;
4471         mAny >>= dMinorUnit;
4472         if( GetProperty( xAxisProp, u"StepHelpCount"_ustr ) )
4473         {
4474             sal_Int32 dMinorUnitCount = 0;
4475             mAny >>= dMinorUnitCount;
4476             // tdf#114168 Don't save minor unit if number of step help count is 5 (which is default for MS Excel),
4477             // to allow proper .xlsx import. If minorUnit is set and majorUnit not, then it is impossible
4478             // to calculate StepHelpCount.
4479             if( dMinorUnitCount != 5 )
4480             {
4481                 pFS->singleElement( FSNS( XML_c, XML_minorUnit ),
4482                     XML_val, OString::number( dMinorUnit ) );
4483             }
4484         }
4485     }
4486 
4487     if( nAxisType == XML_valAx && GetProperty( xAxisProp, u"DisplayUnits"_ustr ) )
4488     {
4489         bool bDisplayUnits = false;
4490         mAny >>= bDisplayUnits;
4491         if(bDisplayUnits)
4492         {
4493             if(GetProperty( xAxisProp, u"BuiltInUnit"_ustr ))
4494             {
4495                 OUString aVal;
4496                 mAny >>= aVal;
4497                 if(!aVal.isEmpty())
4498                 {
4499                     pFS->startElement(FSNS(XML_c, XML_dispUnits));
4500 
4501                     pFS->singleElement(FSNS(XML_c, XML_builtInUnit), XML_val, aVal);
4502 
4503                     pFS->singleElement(FSNS( XML_c, XML_dispUnitsLbl ));
4504                     pFS->endElement( FSNS( XML_c, XML_dispUnits ) );
4505                 }
4506             }
4507         }
4508     }
4509 
4510     pFS->endElement( FSNS( XML_c, nAxisType ) );
4511 }
4512 
exportOneAxis_chartex(const Reference<XPropertySet> & xAxisProp,const Reference<drawing::XShape> & xAxisTitle,const Reference<XPropertySet> & xMajorGrid,const Reference<XPropertySet> & xMinorGrid,sal_Int32 nAxisType,const AxisIdPair & rAxisIdPair)4513 void ChartExport::exportOneAxis_chartex(
4514     const Reference< XPropertySet >& xAxisProp,
4515     const Reference< drawing::XShape >& xAxisTitle,
4516     const Reference< XPropertySet >& xMajorGrid,
4517     const Reference< XPropertySet >& xMinorGrid,
4518     sal_Int32 nAxisType,
4519     const AxisIdPair& rAxisIdPair)
4520 {
4521     FSHelperPtr pFS = GetFS();
4522     pFS->startElement(FSNS(XML_cx, XML_axis), XML_id, OString::number(rAxisIdPair.nAxisId));
4523 
4524     // The following is in the 2010 chart code above:
4525     //    bool bVisible = true;
4526     //    if( xAxisProp.is() )
4527     //    {
4528     //        xAxisProp->getPropertyValue(u"Visible"_ustr) >>=  bVisible;
4529     //    }
4530     //    // only export each axis only once non-deleted
4531     //    auto aItInsertedPair = maExportedAxis.insert(rAxisIdPair.nAxisType);
4532     //    bool bDeleted = !aItInsertedPair.second;
4533     //
4534     //    pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, !bDeleted && bVisible ? "0" : "1");
4535     //
4536     // Is chartex attribute "hidden" the same as !bVisible? And what to do if
4537     // the axis is deleted, per above?
4538 
4539     // ==== catScaling/valScaling
4540     switch (nAxisType) {
4541         case XML_catAx:
4542             pFS->singleElement(FSNS(XML_cx, XML_catScaling) /* TODO: handle gapWidth */);
4543             break;
4544         case XML_valAx:
4545             {
4546                 bool bAutoMax = false;
4547                 double dMax = 0; // Make VS happy
4548                 bool bMaxSpecified = false;
4549                 if(GetProperty( xAxisProp, u"AutoMax"_ustr ) )
4550                     mAny >>= bAutoMax;
4551 
4552                 if( !bAutoMax && (GetProperty( xAxisProp, u"Max"_ustr ) ) )
4553                 {
4554                     mAny >>= dMax;
4555                     bMaxSpecified = true;
4556                 }
4557 
4558                 bool bAutoMin = false;
4559                 double dMin = 0; // Make VS happy
4560                 bool bMinSpecified = false;
4561                 if(GetProperty( xAxisProp, u"AutoMin"_ustr ) )
4562                     mAny >>= bAutoMin;
4563 
4564                 if( !bAutoMin && (GetProperty( xAxisProp, u"Min"_ustr ) ) )
4565                 {
4566                     mAny >>= dMin;
4567                     bMinSpecified = true;
4568                 }
4569 
4570                 // TODO: handle majorUnit/minorUnit in the following
4571                 if (bMaxSpecified && bMinSpecified) {
4572                     pFS->singleElement(FSNS(XML_cx, XML_valScaling),
4573                             XML_max, OString::number(dMax),
4574                             XML_min, OString::number(dMin));
4575                 } else if (!bMaxSpecified && bMinSpecified) {
4576                     pFS->singleElement(FSNS(XML_cx, XML_valScaling),
4577                             XML_min, OString::number(dMin));
4578                 } else if (bMaxSpecified && !bMinSpecified) {
4579                     pFS->singleElement(FSNS(XML_cx, XML_valScaling),
4580                             XML_max, OString::number(dMax));
4581                 } else {
4582                     pFS->singleElement(FSNS(XML_cx, XML_valScaling));
4583                 }
4584 
4585             }
4586             break;
4587         default:
4588             // shouldn't happen
4589             assert(false);
4590     }
4591 
4592     // ==== title
4593     if( xAxisTitle.is() ) {
4594         exportTitle( xAxisTitle, true );
4595     }
4596 
4597     // ==== units
4598     if (GetProperty( xAxisProp, u"DisplayUnits"_ustr ) )
4599     {
4600         bool bDisplayUnits = false;
4601         mAny >>= bDisplayUnits;
4602         if (bDisplayUnits)
4603         {
4604             if (GetProperty( xAxisProp, u"BuiltInUnit"_ustr ))
4605             {
4606                 OUString aVal;
4607                 mAny >>= aVal;
4608                 if(!aVal.isEmpty())
4609                 {
4610                     pFS->startElement(FSNS(XML_cx, XML_units));
4611 
4612                     pFS->startElement(FSNS(XML_cx, XML_unitsLabel));
4613 
4614                     lcl_writeChartexString(pFS, aVal);
4615 
4616                     pFS->endElement(FSNS(XML_cx, XML_unitsLabel));
4617 
4618                     pFS->endElement( FSNS( XML_cx, XML_units ) );
4619                 }
4620             }
4621         }
4622     }
4623 
4624     // ==== majorGridlines
4625     if( xMajorGrid.is())
4626     {
4627         pFS->startElement(FSNS(XML_cx, XML_majorGridlines));
4628         exportShapeProps( xMajorGrid, true );
4629         pFS->endElement( FSNS( XML_cx, XML_majorGridlines ) );
4630     }
4631 
4632     // ==== minorGridlines
4633     if( xMinorGrid.is())
4634     {
4635         pFS->startElement(FSNS(XML_cx, XML_minorGridlines));
4636         exportShapeProps( xMinorGrid, true );
4637         pFS->endElement( FSNS( XML_cx, XML_minorGridlines ) );
4638     }
4639 
4640     // ==== majorTickMarks
4641     if (GetProperty( xAxisProp, u"Marks"_ustr ) )
4642     {
4643         sal_Int32 nValue = 0;
4644         mAny >>= nValue;
4645         pFS->singleElement(FSNS(XML_cx, XML_majorTickMarks), XML_type,
4646                 getTickMarkLocStr(nValue));
4647     }
4648 
4649     // ==== minorTickMarks
4650     if (GetProperty( xAxisProp, u"HelpMarks"_ustr ) )
4651     {
4652         sal_Int32 nValue = 0;
4653         mAny >>= nValue;
4654         pFS->singleElement(FSNS(XML_cx, XML_minorTickMarks), XML_type,
4655                 getTickMarkLocStr(nValue));
4656     }
4657 
4658     // ==== tickLabels consists of nothing but an extLst so I don't know how to
4659     // handle it
4660 
4661     // ==== numFmt
4662     bool bLinkedNumFmt = true;
4663     if (GetProperty(xAxisProp, u"LinkNumberFormatToSource"_ustr))
4664         mAny >>= bLinkedNumFmt;
4665 
4666     OUString aNumberFormatString(u"General"_ustr);
4667     if (GetProperty(xAxisProp, u"NumberFormat"_ustr))
4668     {
4669         sal_Int32 nKey = 0;
4670         mAny >>= nKey;
4671         aNumberFormatString = getNumberFormatCode(nKey);
4672     }
4673 
4674     // We're always outputting this, which presumably isn't necessary, but it's
4675     // not clear what the defaults are for determining if an explicit element is
4676     // needed
4677     pFS->singleElement(FSNS(XML_cx, XML_numFmt),
4678             XML_formatCode, aNumberFormatString,
4679             XML_sourceLinked, bLinkedNumFmt ? "1" : "0");
4680 
4681     // ==== spPr
4682     exportShapeProps( xAxisProp, true );
4683 
4684     // ==== txPr
4685     exportTextProps(xAxisProp, true);
4686 
4687     pFS->endElement( FSNS( XML_cx, XML_axis ) );
4688 }
4689 
4690 namespace {
4691 
4692 struct LabelPlacementParam
4693 {
4694     bool mbExport;
4695     sal_Int32 meDefault;
4696 
4697     std::unordered_set<sal_Int32> maAllowedValues;
4698 
LabelPlacementParamoox::drawingml::__anon0ed582600811::LabelPlacementParam4699     LabelPlacementParam(bool bExport, sal_Int32 nDefault) :
4700         mbExport(bExport),
4701         meDefault(nDefault),
4702         maAllowedValues(
4703           {
4704            css::chart::DataLabelPlacement::OUTSIDE,
4705            css::chart::DataLabelPlacement::INSIDE,
4706            css::chart::DataLabelPlacement::CENTER,
4707            css::chart::DataLabelPlacement::NEAR_ORIGIN,
4708            css::chart::DataLabelPlacement::TOP,
4709            css::chart::DataLabelPlacement::BOTTOM,
4710            css::chart::DataLabelPlacement::LEFT,
4711            css::chart::DataLabelPlacement::RIGHT,
4712            css::chart::DataLabelPlacement::AVOID_OVERLAP
4713           }
4714         )
4715     {}
4716 };
4717 
toOOXMLPlacement(sal_Int32 nPlacement)4718 const char* toOOXMLPlacement( sal_Int32 nPlacement )
4719 {
4720     switch (nPlacement)
4721     {
4722         case css::chart::DataLabelPlacement::OUTSIDE:       return "outEnd";
4723         case css::chart::DataLabelPlacement::INSIDE:        return "inEnd";
4724         case css::chart::DataLabelPlacement::CENTER:        return "ctr";
4725         case css::chart::DataLabelPlacement::NEAR_ORIGIN:   return "inBase";
4726         case css::chart::DataLabelPlacement::TOP:           return "t";
4727         case css::chart::DataLabelPlacement::BOTTOM:        return "b";
4728         case css::chart::DataLabelPlacement::LEFT:          return "l";
4729         case css::chart::DataLabelPlacement::RIGHT:         return "r";
4730         case css::chart::DataLabelPlacement::CUSTOM:
4731         case css::chart::DataLabelPlacement::AVOID_OVERLAP: return "bestFit";
4732         default:
4733             ;
4734     }
4735 
4736     return "outEnd";
4737 }
4738 
getFieldTypeString(const chart2::DataPointCustomLabelFieldType aType)4739 OUString getFieldTypeString( const chart2::DataPointCustomLabelFieldType aType )
4740 {
4741     switch (aType)
4742     {
4743     case chart2::DataPointCustomLabelFieldType_CATEGORYNAME:
4744         return u"CATEGORYNAME"_ustr;
4745 
4746     case chart2::DataPointCustomLabelFieldType_SERIESNAME:
4747         return u"SERIESNAME"_ustr;
4748 
4749     case chart2::DataPointCustomLabelFieldType_VALUE:
4750         return u"VALUE"_ustr;
4751 
4752     case chart2::DataPointCustomLabelFieldType_CELLREF:
4753         return u"CELLREF"_ustr;
4754 
4755     case chart2::DataPointCustomLabelFieldType_CELLRANGE:
4756         return u"CELLRANGE"_ustr;
4757 
4758     default:
4759         break;
4760     }
4761     return OUString();
4762 }
4763 
writeRunProperties(ChartExport * pChartExport,Reference<XPropertySet> const & xPropertySet)4764 void writeRunProperties( ChartExport* pChartExport, Reference<XPropertySet> const & xPropertySet )
4765 {
4766     bool bDummy = false;
4767     sal_Int32 nDummy;
4768     pChartExport->WriteRunProperties(xPropertySet, false, XML_rPr, true, bDummy, nDummy);
4769 }
4770 
writeCustomLabel(const FSHelperPtr & pFS,ChartExport * pChartExport,const Sequence<Reference<chart2::XDataPointCustomLabelField>> & rCustomLabelFields,sal_Int32 nLabelIndex,DataLabelsRange & rDLblsRange)4771 void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport,
4772                        const Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields,
4773                        sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange )
4774 {
4775     pFS->startElement(FSNS(XML_c, XML_tx));
4776     pFS->startElement(FSNS(XML_c, XML_rich));
4777 
4778     // TODO: body properties?
4779     pFS->singleElement(FSNS(XML_a, XML_bodyPr));
4780 
4781     OUString sFieldType;
4782     OUString sContent;
4783     pFS->startElement(FSNS(XML_a, XML_p));
4784 
4785     for (auto& rField : rCustomLabelFields)
4786     {
4787         Reference<XPropertySet> xPropertySet(rField, UNO_QUERY);
4788         chart2::DataPointCustomLabelFieldType aType = rField->getFieldType();
4789         sFieldType.clear();
4790         sContent.clear();
4791         bool bNewParagraph = false;
4792 
4793         if (aType == chart2::DataPointCustomLabelFieldType_CELLRANGE &&
4794             rField->getDataLabelsRange())
4795         {
4796             if (rDLblsRange.getRange().isEmpty())
4797                 rDLblsRange.setRange(rField->getCellRange());
4798 
4799             if (!rDLblsRange.hasLabel(nLabelIndex))
4800                 rDLblsRange.setLabel(nLabelIndex, rField->getString());
4801 
4802             sContent = "[CELLRANGE]";
4803         }
4804         else
4805         {
4806             sContent = rField->getString();
4807         }
4808 
4809         if (aType == chart2::DataPointCustomLabelFieldType_NEWLINE)
4810             bNewParagraph = true;
4811         else if (aType != chart2::DataPointCustomLabelFieldType_TEXT)
4812             sFieldType = getFieldTypeString(aType);
4813 
4814         if (bNewParagraph)
4815         {
4816             pFS->endElement(FSNS(XML_a, XML_p));
4817             pFS->startElement(FSNS(XML_a, XML_p));
4818             continue;
4819         }
4820 
4821         if (sFieldType.isEmpty())
4822         {
4823             // Normal text run
4824             pFS->startElement(FSNS(XML_a, XML_r));
4825             writeRunProperties(pChartExport, xPropertySet);
4826 
4827             pFS->startElement(FSNS(XML_a, XML_t));
4828             pFS->writeEscaped(sContent);
4829             pFS->endElement(FSNS(XML_a, XML_t));
4830 
4831             pFS->endElement(FSNS(XML_a, XML_r));
4832         }
4833         else
4834         {
4835             // Field
4836             pFS->startElement(FSNS(XML_a, XML_fld), XML_id, rField->getGuid(), XML_type,
4837                               sFieldType);
4838             writeRunProperties(pChartExport, xPropertySet);
4839 
4840             pFS->startElement(FSNS(XML_a, XML_t));
4841             pFS->writeEscaped(sContent);
4842             pFS->endElement(FSNS(XML_a, XML_t));
4843 
4844             pFS->endElement(FSNS(XML_a, XML_fld));
4845         }
4846     }
4847 
4848     pFS->endElement(FSNS(XML_a, XML_p));
4849     pFS->endElement(FSNS(XML_c, XML_rich));
4850     pFS->endElement(FSNS(XML_c, XML_tx));
4851 }
4852 
writeLabelProperties(const FSHelperPtr & pFS,ChartExport * pChartExport,const uno::Reference<beans::XPropertySet> & xPropSet,const LabelPlacementParam & rLabelParam,sal_Int32 nLabelIndex,DataLabelsRange & rDLblsRange,bool bIsChartex)4853 void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport,
4854     const uno::Reference<beans::XPropertySet>& xPropSet, const LabelPlacementParam& rLabelParam,
4855     sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange,
4856     bool bIsChartex)
4857 {
4858     if (!xPropSet.is())
4859         return;
4860 
4861     const sal_Int32 nChartNS = bIsChartex ? XML_cx : XML_c;
4862 
4863     chart2::DataPointLabel aLabel;
4864     Sequence<Reference<chart2::XDataPointCustomLabelField>> aCustomLabelFields;
4865     sal_Int32 nLabelBorderWidth = 0;
4866     sal_Int32 nLabelBorderColor = 0x00FFFFFF;
4867     sal_Int32 nLabelFillColor = -1;
4868 
4869     xPropSet->getPropertyValue(u"Label"_ustr) >>= aLabel;
4870     xPropSet->getPropertyValue(u"CustomLabelFields"_ustr) >>= aCustomLabelFields;
4871     xPropSet->getPropertyValue(u"LabelBorderWidth"_ustr) >>= nLabelBorderWidth;
4872     xPropSet->getPropertyValue(u"LabelBorderColor"_ustr) >>= nLabelBorderColor;
4873     xPropSet->getPropertyValue(u"LabelFillColor"_ustr) >>= nLabelFillColor;
4874 
4875     if (nLabelBorderWidth > 0 || nLabelFillColor != -1)
4876     {
4877         pFS->startElement(FSNS(nChartNS, XML_spPr));
4878 
4879         if (nLabelFillColor != -1)
4880         {
4881             ::Color nColor(ColorTransparency, nLabelFillColor);
4882             if (nColor.IsTransparent())
4883                 pChartExport->WriteSolidFill(nColor, nColor.GetAlpha());
4884             else
4885                 pChartExport->WriteSolidFill(nColor);
4886         }
4887 
4888         if (nLabelBorderWidth > 0)
4889         {
4890             pFS->startElement(FSNS(XML_a, XML_ln), XML_w,
4891                               OString::number(convertHmmToEmu(nLabelBorderWidth)));
4892 
4893             if (nLabelBorderColor != -1)
4894             {
4895                 ::Color nColor(ColorTransparency, nLabelBorderColor);
4896                 if (nColor.IsTransparent())
4897                     pChartExport->WriteSolidFill(nColor, nColor.GetAlpha());
4898                 else
4899                     pChartExport->WriteSolidFill(nColor);
4900             }
4901 
4902             pFS->endElement(FSNS(XML_a, XML_ln));
4903         }
4904 
4905         pFS->endElement(FSNS(nChartNS, XML_spPr));
4906     }
4907 
4908     pChartExport->exportTextProps(xPropSet, bIsChartex);
4909 
4910     if (aCustomLabelFields.hasElements())
4911         writeCustomLabel(pFS, pChartExport, aCustomLabelFields, nLabelIndex, rDLblsRange);
4912 
4913     if (!bIsChartex) {
4914         // In chartex label position is an attribute of cx:dataLabel
4915         if (rLabelParam.mbExport)
4916         {
4917             sal_Int32 nLabelPlacement = rLabelParam.meDefault;
4918             if (xPropSet->getPropertyValue(u"LabelPlacement"_ustr) >>= nLabelPlacement)
4919             {
4920                 if (!rLabelParam.maAllowedValues.count(nLabelPlacement))
4921                     nLabelPlacement = rLabelParam.meDefault;
4922                 pFS->singleElement(FSNS(XML_c, XML_dLblPos), XML_val, toOOXMLPlacement(nLabelPlacement));
4923             }
4924         }
4925 
4926         pFS->singleElement(FSNS(XML_c, XML_showLegendKey), XML_val, ToPsz10(aLabel.ShowLegendSymbol));
4927         pFS->singleElement(FSNS(XML_c, XML_showVal), XML_val, ToPsz10(aLabel.ShowNumber));
4928         pFS->singleElement(FSNS(XML_c, XML_showCatName), XML_val, ToPsz10(aLabel.ShowCategoryName));
4929         pFS->singleElement(FSNS(XML_c, XML_showSerName), XML_val, ToPsz10(aLabel.ShowSeriesName));
4930         pFS->singleElement(FSNS(XML_c, XML_showPercent), XML_val, ToPsz10(aLabel.ShowNumberInPercent));
4931     }
4932 
4933     // Export the text "separator" if exists
4934     uno::Any aAny = xPropSet->getPropertyValue(u"LabelSeparator"_ustr);
4935     if( aAny.hasValue() )
4936     {
4937         OUString nLabelSeparator;
4938         aAny >>= nLabelSeparator;
4939         pFS->startElement(FSNS(nChartNS, XML_separator));
4940         pFS->writeEscaped( nLabelSeparator );
4941         pFS->endElement( FSNS(nChartNS, XML_separator ) );
4942     }
4943 
4944     if (rDLblsRange.hasLabel(nLabelIndex))
4945     {
4946         pFS->startElement(FSNS(nChartNS, XML_extLst));
4947         // TODO: is the following correct for chartex?
4948         pFS->startElement(FSNS(nChartNS, XML_ext), XML_uri,
4949             "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15),
4950             pChartExport->GetFB()->getNamespaceURL(OOX_NS(c15)));
4951 
4952         pFS->singleElement(FSNS(XML_c15, XML_showDataLabelsRange), XML_val, "1");
4953 
4954         pFS->endElement(FSNS(nChartNS, XML_ext));
4955         pFS->endElement(FSNS(nChartNS, XML_extLst));
4956     }
4957 }
4958 
4959 }
4960 
exportDataLabels(const uno::Reference<chart2::XDataSeries> & xSeries,sal_Int32 nSeriesLength,sal_Int32 eChartType,DataLabelsRange & rDLblsRange,bool bIsChartex)4961 void ChartExport::exportDataLabels(
4962     const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType,
4963     DataLabelsRange& rDLblsRange,
4964     bool bIsChartex)
4965 {
4966     if (!xSeries.is() || nSeriesLength <= 0)
4967         return;
4968 
4969     uno::Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
4970     if (!xPropSet.is())
4971         return;
4972 
4973     FSHelperPtr pFS = GetFS();
4974 
4975     if (bIsChartex) {
4976         pFS->startElement(FSNS(XML_cx, XML_dataLabels));
4977     } else {
4978         pFS->startElement(FSNS(XML_c, XML_dLbls));
4979     }
4980 
4981     bool bLinkedNumFmt = true;
4982     if (GetProperty(xPropSet, u"LinkNumberFormatToSource"_ustr))
4983         mAny >>= bLinkedNumFmt;
4984 
4985     chart2::DataPointLabel aLabel;
4986     bool bLabelIsNumberFormat = true;
4987     if( xPropSet->getPropertyValue(u"Label"_ustr) >>= aLabel )
4988         bLabelIsNumberFormat = aLabel.ShowNumber;
4989 
4990     if (GetProperty(xPropSet, bLabelIsNumberFormat ? u"NumberFormat"_ustr : u"PercentageNumberFormat"_ustr))
4991     {
4992         sal_Int32 nKey = 0;
4993         mAny >>= nKey;
4994 
4995         OUString aNumberFormatString = getNumberFormatCode(nKey);
4996 
4997         if (bIsChartex) {
4998             pFS->singleElement(FSNS(XML_cx, XML_numFmt),
4999                 XML_formatCode, aNumberFormatString,
5000                 XML_sourceLinked, ToPsz10(bLinkedNumFmt));
5001         } else {
5002             pFS->singleElement(FSNS(XML_c, XML_numFmt),
5003                 XML_formatCode, aNumberFormatString,
5004                 XML_sourceLinked, ToPsz10(bLinkedNumFmt));
5005         }
5006     }
5007 
5008     uno::Sequence<sal_Int32> aAttrLabelIndices;
5009     xPropSet->getPropertyValue(u"AttributedDataPoints"_ustr) >>= aAttrLabelIndices;
5010 
5011     // We must not export label placement property when the chart type doesn't
5012     // support this option in MS Office, else MS Office would think the file
5013     // is corrupt & refuse to open it.
5014 
5015     const chart::TypeGroupInfo& rInfo = chart::GetTypeGroupInfo(static_cast<chart::TypeId>(eChartType));
5016     LabelPlacementParam aParam(!mbIs3DChart, rInfo.mnDefLabelPos);
5017     switch (eChartType) // diagram chart type
5018     {
5019         case chart::TYPEID_PIE:
5020             if(getChartType() == chart::TYPEID_DOUGHNUT)
5021                 aParam.mbExport = false;
5022             else
5023             // All pie charts support label placement.
5024             aParam.mbExport = true;
5025         break;
5026         case chart::TYPEID_AREA:
5027         case chart::TYPEID_RADARLINE:
5028         case chart::TYPEID_RADARAREA:
5029             // These chart types don't support label placement.
5030             aParam.mbExport = false;
5031         break;
5032         case chart::TYPEID_BAR:
5033             if (mbStacked || mbPercent)
5034             {
5035                 aParam.maAllowedValues.clear();
5036                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
5037                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
5038                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
5039                 aParam.meDefault = css::chart::DataLabelPlacement::CENTER;
5040             }
5041             else  // Clustered bar chart
5042             {
5043                 aParam.maAllowedValues.clear();
5044                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
5045                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
5046                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::OUTSIDE);
5047                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
5048                 aParam.meDefault = css::chart::DataLabelPlacement::OUTSIDE;
5049             }
5050         break;
5051         // TODO: How do chartex charts handle this?
5052         default:
5053             ;
5054     }
5055 
5056     for (const sal_Int32 nIdx : aAttrLabelIndices)
5057     {
5058         uno::Reference<beans::XPropertySet> xLabelPropSet = xSeries->getDataPointByIndex(nIdx);
5059 
5060         if (!xLabelPropSet.is())
5061             continue;
5062 
5063         if (bIsChartex) {
5064             if (aParam.mbExport)
5065             {
5066                 sal_Int32 nLabelPlacement = aParam.meDefault;
5067                 if (xPropSet->getPropertyValue(u"LabelPlacement"_ustr) >>= nLabelPlacement)
5068                 {
5069                     if (!aParam.maAllowedValues.count(nLabelPlacement))
5070                         nLabelPlacement = aParam.meDefault;
5071                     pFS->startElement(FSNS(XML_cx, XML_dataLabel),
5072                             XML_idx, OString::number(nIdx),
5073                             XML_pos, toOOXMLPlacement(nLabelPlacement));
5074                 }
5075             } else {
5076                 pFS->startElement(FSNS(XML_cx, XML_dataLabel), XML_idx, OString::number(nIdx));
5077             }
5078         } else {
5079             pFS->startElement(FSNS(XML_c, XML_dLbl));
5080             pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nIdx));
5081 
5082             // As far as i know there can be issues with the Positions,
5083             // if a piechart label use AVOID_OVERLAP placement (== BestFit)
5084             // because LO and MS may calculate the bestFit positions differently.
5085             bool bWritePosition = true;
5086             if (eChartType == chart::TYPEID_PIE)
5087             {
5088                 sal_Int32 nLabelPlacement = aParam.meDefault;
5089                 xLabelPropSet->getPropertyValue(u"LabelPlacement"_ustr) >>= nLabelPlacement;
5090                 if (nLabelPlacement == css::chart::DataLabelPlacement::AVOID_OVERLAP)
5091                     bWritePosition = false;
5092             }
5093 
5094             // export custom position of data label
5095             if (bWritePosition)
5096             {
5097                 chart2::RelativePosition aCustomLabelPosition;
5098                 if( xLabelPropSet->getPropertyValue(u"CustomLabelPosition"_ustr) >>= aCustomLabelPosition )
5099                 {
5100                     pFS->startElement(FSNS(XML_c, XML_layout));
5101                     pFS->startElement(FSNS(XML_c, XML_manualLayout));
5102 
5103                     pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(aCustomLabelPosition.Primary));
5104                     pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(aCustomLabelPosition.Secondary));
5105 
5106                     SAL_WARN_IF(aCustomLabelPosition.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position");
5107 
5108                     pFS->endElement(FSNS(XML_c, XML_manualLayout));
5109                     pFS->endElement(FSNS(XML_c, XML_layout));
5110                 }
5111             }
5112         }
5113 
5114         if( GetProperty(xLabelPropSet, u"LinkNumberFormatToSource"_ustr) )
5115             mAny >>= bLinkedNumFmt;
5116 
5117         if( xLabelPropSet->getPropertyValue(u"Label"_ustr) >>= aLabel )
5118             bLabelIsNumberFormat = aLabel.ShowNumber;
5119         else
5120             bLabelIsNumberFormat = true;
5121 
5122         if (GetProperty(xLabelPropSet, bLabelIsNumberFormat ? u"NumberFormat"_ustr : u"PercentageNumberFormat"_ustr))
5123         {
5124             sal_Int32 nKey = 0;
5125             mAny >>= nKey;
5126 
5127             OUString aNumberFormatString = getNumberFormatCode(nKey);
5128 
5129             if (bIsChartex) {
5130                 pFS->singleElement(FSNS(XML_cx, XML_numFmt), XML_formatCode, aNumberFormatString,
5131                                    XML_sourceLinked, ToPsz10(bLinkedNumFmt));
5132             } else {
5133                 pFS->singleElement(FSNS(XML_c, XML_numFmt), XML_formatCode, aNumberFormatString,
5134                                    XML_sourceLinked, ToPsz10(bLinkedNumFmt));
5135             }
5136         }
5137 
5138         // Individual label property that overwrites the baseline.
5139         writeLabelProperties(pFS, this, xLabelPropSet, aParam, nIdx,
5140                 rDLblsRange, bIsChartex);
5141         pFS->endElement(FSNS(XML_c, XML_dLbl));
5142     }
5143 
5144     // Baseline label properties for all labels.
5145     writeLabelProperties(pFS, this, xPropSet, aParam, -1, rDLblsRange,
5146             bIsChartex);
5147 
5148     if (!bIsChartex) {
5149         bool bShowLeaderLines = false;
5150         xPropSet->getPropertyValue(u"ShowCustomLeaderLines"_ustr) >>= bShowLeaderLines;
5151         pFS->singleElement(FSNS(XML_c, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines));
5152 
5153         // Export LeaderLine properties
5154         // TODO: import all kind of LeaderLine props (not just LineColor/LineWidth)
5155         if (bShowLeaderLines)
5156         {
5157             pFS->startElement(FSNS(XML_c, XML_leaderLines));
5158             pFS->startElement(FSNS(XML_c, XML_spPr));
5159             WriteOutline(xPropSet, getModel());
5160             pFS->endElement(FSNS(XML_c, XML_spPr));
5161             pFS->endElement(FSNS(XML_c, XML_leaderLines));
5162         }
5163 
5164         // Export leader line
5165         if( eChartType != chart::TYPEID_PIE )
5166         {
5167             pFS->startElement(FSNS(XML_c, XML_extLst));
5168             pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15), GetFB()->getNamespaceURL(OOX_NS(c15)));
5169             pFS->singleElement(FSNS(XML_c15, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines));
5170             pFS->endElement(FSNS(XML_c, XML_ext));
5171             pFS->endElement(FSNS(XML_c, XML_extLst));
5172         }
5173     }
5174 
5175     if (bIsChartex) {
5176         pFS->endElement(FSNS(XML_cx, XML_dataLabels));
5177     } else {
5178         pFS->endElement(FSNS(XML_c, XML_dLbls));
5179     }
5180 }
5181 
exportDataPoints(const uno::Reference<beans::XPropertySet> & xSeriesProperties,sal_Int32 nSeriesLength,sal_Int32 eChartType)5182 void ChartExport::exportDataPoints(
5183     const uno::Reference< beans::XPropertySet > & xSeriesProperties,
5184     sal_Int32 nSeriesLength, sal_Int32 eChartType )
5185 {
5186     uno::Reference< chart2::XDataSeries > xSeries( xSeriesProperties, uno::UNO_QUERY );
5187     bool bVaryColorsByPoint = false;
5188     Sequence< sal_Int32 > aDataPointSeq;
5189     if( xSeriesProperties.is())
5190     {
5191         Any aAny = xSeriesProperties->getPropertyValue( u"AttributedDataPoints"_ustr );
5192         aAny >>= aDataPointSeq;
5193         xSeriesProperties->getPropertyValue( u"VaryColorsByPoint"_ustr ) >>= bVaryColorsByPoint;
5194     }
5195 
5196     const sal_Int32 * pPoints = aDataPointSeq.getConstArray();
5197     sal_Int32 nElement;
5198     Reference< chart2::XColorScheme > xColorScheme;
5199     if( mxNewDiagram.is())
5200         xColorScheme.set( mxNewDiagram->getDefaultColorScheme());
5201 
5202     if( bVaryColorsByPoint && xColorScheme.is() )
5203     {
5204         o3tl::sorted_vector< sal_Int32 > aAttrPointSet;
5205         aAttrPointSet.reserve(aDataPointSeq.getLength());
5206         for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p)
5207             aAttrPointSet.insert(*p);
5208         const auto aEndIt = aAttrPointSet.end();
5209         for( nElement = 0; nElement < nSeriesLength; ++nElement )
5210         {
5211             uno::Reference< beans::XPropertySet > xPropSet;
5212             if( aAttrPointSet.find( nElement ) != aEndIt )
5213             {
5214                 try
5215                 {
5216                     xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
5217                             xSeries, nElement, getModel() );
5218                 }
5219                 catch( const uno::Exception & )
5220                 {
5221                     DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
5222                 }
5223             }
5224             else
5225             {
5226                 // property set only containing the color
5227                 xPropSet.set( new ColorPropertySet( ColorTransparency, xColorScheme->getColorByIndex( nElement )));
5228             }
5229 
5230             if( xPropSet.is() )
5231             {
5232                 FSHelperPtr pFS = GetFS();
5233                 pFS->startElement(FSNS(XML_c, XML_dPt));
5234                 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
5235 
5236                 switch (eChartType)
5237                 {
5238                     case chart::TYPEID_PIE:
5239                     case chart::TYPEID_DOUGHNUT:
5240                     {
5241                         if( xPropSet.is() && GetProperty( xPropSet, u"SegmentOffset"_ustr) )
5242                         {
5243                             sal_Int32 nOffset = 0;
5244                             mAny >>= nOffset;
5245                             if (nOffset)
5246                                 pFS->singleElement( FSNS( XML_c, XML_explosion ),
5247                                         XML_val, OString::number( nOffset ) );
5248                         }
5249                         break;
5250                     }
5251                     default:
5252                         break;
5253                 }
5254                 exportShapeProps( xPropSet, false );
5255 
5256                 pFS->endElement( FSNS( XML_c, XML_dPt ) );
5257             }
5258         }
5259     }
5260 
5261     // Export Data Point Property in Charts even if the VaryColors is false
5262     if( bVaryColorsByPoint )
5263         return;
5264 
5265     o3tl::sorted_vector< sal_Int32 > aAttrPointSet;
5266     aAttrPointSet.reserve(aDataPointSeq.getLength());
5267     for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p)
5268         aAttrPointSet.insert(*p);
5269     const auto aEndIt = aAttrPointSet.end();
5270     for( nElement = 0; nElement < nSeriesLength; ++nElement )
5271     {
5272         uno::Reference< beans::XPropertySet > xPropSet;
5273         if( aAttrPointSet.find( nElement ) != aEndIt )
5274         {
5275             try
5276             {
5277                 xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
5278                         xSeries, nElement, getModel() );
5279             }
5280             catch( const uno::Exception & )
5281             {
5282                 DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
5283             }
5284         }
5285 
5286         if( xPropSet.is() )
5287         {
5288             FSHelperPtr pFS = GetFS();
5289             pFS->startElement(FSNS(XML_c, XML_dPt));
5290             pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
5291 
5292             switch( eChartType )
5293             {
5294                 case chart::TYPEID_BUBBLE:
5295                 case chart::TYPEID_HORBAR:
5296                 case chart::TYPEID_BAR:
5297                     pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
5298                     exportShapeProps(xPropSet, false);
5299                     break;
5300 
5301                 case chart::TYPEID_LINE:
5302                 case chart::TYPEID_SCATTER:
5303                 case chart::TYPEID_RADARLINE:
5304                     exportMarker(xPropSet);
5305                     break;
5306 
5307                 default:
5308                     exportShapeProps(xPropSet, false);
5309                     break;
5310             }
5311 
5312             pFS->endElement( FSNS( XML_c, XML_dPt ) );
5313         }
5314     }
5315 }
5316 
5317 // Generalized axis output
createAxes(bool bPrimaryAxes,bool bCheckCombinedAxes)5318 void ChartExport::createAxes(bool bPrimaryAxes, bool bCheckCombinedAxes)
5319 {
5320     sal_Int32 nAxisIdx, nAxisIdy;
5321     bool bPrimaryAxisExists = false;
5322     bool bSecondaryAxisExists = false;
5323     // let's check which axis already exists and which axis is attached to the actual dataseries
5324     if (maAxes.size() >= 2)
5325     {
5326         bPrimaryAxisExists = bPrimaryAxes && maAxes[1].nAxisType == AXIS_PRIMARY_Y;
5327         bSecondaryAxisExists = !bPrimaryAxes && maAxes[1].nAxisType == AXIS_SECONDARY_Y;
5328     }
5329     // tdf#114181 keep axes of combined charts
5330     if ( bCheckCombinedAxes && ( bPrimaryAxisExists || bSecondaryAxisExists ) )
5331     {
5332         nAxisIdx = maAxes[0].nAxisId;
5333         nAxisIdy = maAxes[1].nAxisId;
5334     }
5335     else
5336     {
5337         nAxisIdx = lcl_generateRandomValue();
5338         nAxisIdy = lcl_generateRandomValue();
5339         AxesType eXAxis = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X;
5340         AxesType eYAxis = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y;
5341         maAxes.emplace_back( eXAxis, nAxisIdx, nAxisIdy );
5342         maAxes.emplace_back( eYAxis, nAxisIdy, nAxisIdx );
5343     }
5344     // Export IDs
5345     FSHelperPtr pFS = GetFS();
5346     pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdx));
5347     pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdy));
5348     if (mbHasZAxis)
5349     {
5350         sal_Int32 nAxisIdz = 0;
5351         if( isDeep3dChart() )
5352         {
5353             nAxisIdz = lcl_generateRandomValue();
5354             maAxes.emplace_back( AXIS_PRIMARY_Z, nAxisIdz, nAxisIdy );
5355         }
5356         pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdz));
5357     }
5358 }
5359 
exportGrouping(bool isBar)5360 void ChartExport::exportGrouping( bool isBar )
5361 {
5362     FSHelperPtr pFS = GetFS();
5363     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
5364     // grouping
5365     if( GetProperty( xPropSet, u"Stacked"_ustr ) )
5366         mAny >>= mbStacked;
5367     if( GetProperty( xPropSet, u"Percent"_ustr ) )
5368         mAny >>= mbPercent;
5369 
5370     const char* grouping = nullptr;
5371     if (mbStacked)
5372         grouping = "stacked";
5373     else if (mbPercent)
5374         grouping = "percentStacked";
5375     else
5376     {
5377         if( isBar && !isDeep3dChart() )
5378         {
5379             grouping = "clustered";
5380         }
5381         else
5382             grouping = "standard";
5383     }
5384     pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, grouping);
5385 }
5386 
exportTrendlines(const Reference<chart2::XDataSeries> & xSeries)5387 void ChartExport::exportTrendlines( const Reference< chart2::XDataSeries >& xSeries )
5388 {
5389     FSHelperPtr pFS = GetFS();
5390     Reference< chart2::XRegressionCurveContainer > xRegressionCurveContainer( xSeries, UNO_QUERY );
5391     if( !xRegressionCurveContainer.is() )
5392         return;
5393 
5394     const Sequence< Reference< chart2::XRegressionCurve > > aRegCurveSeq = xRegressionCurveContainer->getRegressionCurves();
5395     for( const Reference< chart2::XRegressionCurve >& xRegCurve : aRegCurveSeq )
5396     {
5397         if (!xRegCurve.is())
5398             continue;
5399 
5400         Reference< XPropertySet > xProperties( xRegCurve , uno::UNO_QUERY );
5401 
5402         OUString aService;
5403         Reference< lang::XServiceName > xServiceName( xProperties, UNO_QUERY );
5404         if( !xServiceName.is() )
5405             continue;
5406 
5407         aService = xServiceName->getServiceName();
5408 
5409         if(aService != "com.sun.star.chart2.LinearRegressionCurve" &&
5410                 aService != "com.sun.star.chart2.ExponentialRegressionCurve" &&
5411                 aService != "com.sun.star.chart2.LogarithmicRegressionCurve" &&
5412                 aService != "com.sun.star.chart2.PotentialRegressionCurve" &&
5413                 aService != "com.sun.star.chart2.PolynomialRegressionCurve" &&
5414                 aService != "com.sun.star.chart2.MovingAverageRegressionCurve")
5415             continue;
5416 
5417         pFS->startElement(FSNS(XML_c, XML_trendline));
5418 
5419         OUString aName;
5420         xProperties->getPropertyValue(u"CurveName"_ustr) >>= aName;
5421         if(!aName.isEmpty())
5422         {
5423             pFS->startElement(FSNS(XML_c, XML_name));
5424             pFS->writeEscaped(aName);
5425             pFS->endElement( FSNS( XML_c, XML_name) );
5426         }
5427 
5428         exportShapeProps( xProperties, false );
5429 
5430         if( aService == "com.sun.star.chart2.LinearRegressionCurve" )
5431         {
5432             pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "linear");
5433         }
5434         else if( aService == "com.sun.star.chart2.ExponentialRegressionCurve" )
5435         {
5436             pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "exp");
5437         }
5438         else if( aService == "com.sun.star.chart2.LogarithmicRegressionCurve" )
5439         {
5440             pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "log");
5441         }
5442         else if( aService == "com.sun.star.chart2.PotentialRegressionCurve" )
5443         {
5444             pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "power");
5445         }
5446         else if( aService == "com.sun.star.chart2.PolynomialRegressionCurve" )
5447         {
5448             pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "poly");
5449 
5450             sal_Int32 aDegree = 2;
5451             xProperties->getPropertyValue( u"PolynomialDegree"_ustr) >>= aDegree;
5452             pFS->singleElement(FSNS(XML_c, XML_order), XML_val, OString::number(aDegree));
5453         }
5454         else if( aService == "com.sun.star.chart2.MovingAverageRegressionCurve" )
5455         {
5456             pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "movingAvg");
5457 
5458             sal_Int32 aPeriod = 2;
5459             xProperties->getPropertyValue( u"MovingAveragePeriod"_ustr) >>= aPeriod;
5460 
5461             pFS->singleElement(FSNS(XML_c, XML_period), XML_val, OString::number(aPeriod));
5462         }
5463         else
5464         {
5465             // should never happen
5466             // This would produce invalid OOXML files so we check earlier for the type
5467             assert(false);
5468         }
5469 
5470         double fExtrapolateForward = 0.0;
5471         double fExtrapolateBackward = 0.0;
5472 
5473         xProperties->getPropertyValue(u"ExtrapolateForward"_ustr) >>= fExtrapolateForward;
5474         xProperties->getPropertyValue(u"ExtrapolateBackward"_ustr) >>= fExtrapolateBackward;
5475 
5476         pFS->singleElement( FSNS( XML_c, XML_forward ),
5477                 XML_val, OString::number(fExtrapolateForward) );
5478 
5479         pFS->singleElement( FSNS( XML_c, XML_backward ),
5480                 XML_val, OString::number(fExtrapolateBackward) );
5481 
5482         bool bForceIntercept = false;
5483         xProperties->getPropertyValue(u"ForceIntercept"_ustr) >>= bForceIntercept;
5484 
5485         if (bForceIntercept)
5486         {
5487             double fInterceptValue = 0.0;
5488             xProperties->getPropertyValue(u"InterceptValue"_ustr) >>= fInterceptValue;
5489 
5490             pFS->singleElement( FSNS( XML_c, XML_intercept ),
5491                 XML_val, OString::number(fInterceptValue) );
5492         }
5493 
5494         // Equation properties
5495         Reference< XPropertySet > xEquationProperties( xRegCurve->getEquationProperties() );
5496 
5497         // Show Equation
5498         bool bShowEquation = false;
5499         xEquationProperties->getPropertyValue(u"ShowEquation"_ustr) >>= bShowEquation;
5500 
5501         // Show R^2
5502         bool bShowCorrelationCoefficient = false;
5503         xEquationProperties->getPropertyValue(u"ShowCorrelationCoefficient"_ustr) >>= bShowCorrelationCoefficient;
5504 
5505         pFS->singleElement( FSNS( XML_c, XML_dispRSqr ),
5506                 XML_val, ToPsz10(bShowCorrelationCoefficient) );
5507 
5508         pFS->singleElement(FSNS(XML_c, XML_dispEq), XML_val, ToPsz10(bShowEquation));
5509 
5510         pFS->endElement( FSNS( XML_c, XML_trendline ) );
5511     }
5512 }
5513 
exportMarker(const Reference<XPropertySet> & xPropSet)5514 void ChartExport::exportMarker(const Reference< XPropertySet >& xPropSet)
5515 {
5516     chart2::Symbol aSymbol;
5517     if( GetProperty( xPropSet, u"Symbol"_ustr ) )
5518         mAny >>= aSymbol;
5519 
5520     if(aSymbol.Style != chart2::SymbolStyle_STANDARD && aSymbol.Style != chart2::SymbolStyle_NONE)
5521         return;
5522 
5523     FSHelperPtr pFS = GetFS();
5524     pFS->startElement(FSNS(XML_c, XML_marker));
5525 
5526     sal_Int32 nSymbol = aSymbol.StandardSymbol;
5527     // TODO: more properties support for marker
5528     const char* pSymbolType; // no initialization here, to let compiler warn if we have a code path
5529                              // where it stays uninitialized
5530     switch( nSymbol )
5531     {
5532         case 0:
5533             pSymbolType = "square";
5534             break;
5535         case 1:
5536             pSymbolType = "diamond";
5537             break;
5538         case 2:
5539         case 3:
5540         case 4:
5541         case 5:
5542             pSymbolType = "triangle";
5543             break;
5544         case 8:
5545             pSymbolType = "circle";
5546             break;
5547         case 9:
5548             pSymbolType = "star";
5549             break;
5550         case 10:
5551             pSymbolType = "x"; // in MS office 2010 built in symbol marker 'X' is represented as 'x'
5552             break;
5553         case 11:
5554             pSymbolType = "plus";
5555             break;
5556         case 13:
5557             pSymbolType = "dash";
5558             break;
5559         default:
5560             pSymbolType = "square";
5561             break;
5562     }
5563 
5564     bool bSkipFormatting = false;
5565     if (aSymbol.Style == chart2::SymbolStyle_NONE)
5566     {
5567         bSkipFormatting = true;
5568         pSymbolType = "none";
5569     }
5570 
5571     pFS->singleElement(FSNS(XML_c, XML_symbol), XML_val, pSymbolType);
5572 
5573     if (!bSkipFormatting)
5574     {
5575         awt::Size aSymbolSize = aSymbol.Size;
5576         sal_Int32 nSize = std::max( aSymbolSize.Width, aSymbolSize.Height );
5577 
5578         nSize = nSize/250.0*7.0 + 1; // just guessed based on some test cases,
5579         //the value is always 1 less than the actual value.
5580         nSize = std::clamp( int(nSize), 2, 72 );
5581         pFS->singleElement(FSNS(XML_c, XML_size), XML_val, OString::number(nSize));
5582 
5583         pFS->startElement(FSNS(XML_c, XML_spPr));
5584 
5585         util::Color aColor = aSymbol.FillColor;
5586         if (GetProperty(xPropSet, u"Color"_ustr))
5587             mAny >>= aColor;
5588 
5589         if (aColor == -1)
5590         {
5591             pFS->singleElement(FSNS(XML_a, XML_noFill));
5592         }
5593         else
5594             WriteSolidFill(::Color(ColorTransparency, aColor));
5595 
5596         pFS->endElement( FSNS( XML_c, XML_spPr ) );
5597     }
5598 
5599     pFS->endElement( FSNS( XML_c, XML_marker ) );
5600 }
5601 
exportSmooth()5602 void ChartExport::exportSmooth()
5603 {
5604     FSHelperPtr pFS = GetFS();
5605     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY );
5606     sal_Int32 nSplineType = 0;
5607     if( GetProperty( xPropSet, u"SplineType"_ustr ) )
5608         mAny >>= nSplineType;
5609     const char* pVal = nSplineType != 0 ? "1" : "0";
5610     pFS->singleElement(FSNS(XML_c, XML_smooth), XML_val, pVal);
5611 }
5612 
exportFirstSliceAng()5613 void ChartExport::exportFirstSliceAng( )
5614 {
5615     FSHelperPtr pFS = GetFS();
5616     sal_Int32 nStartingAngle = 0;
5617     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
5618     if( GetProperty( xPropSet, u"StartingAngle"_ustr ) )
5619         mAny >>= nStartingAngle;
5620 
5621     // convert to ooxml angle
5622     nStartingAngle = (450 - nStartingAngle ) % 360;
5623     pFS->singleElement(FSNS(XML_c, XML_firstSliceAng), XML_val, OString::number(nStartingAngle));
5624 }
5625 
5626 namespace {
5627 
getErrorBarStyle(sal_Int32 nErrorBarStyle)5628 const char* getErrorBarStyle(sal_Int32 nErrorBarStyle)
5629 {
5630     switch(nErrorBarStyle)
5631     {
5632         case cssc::ErrorBarStyle::NONE:
5633             return nullptr;
5634         case cssc::ErrorBarStyle::VARIANCE:
5635             break;
5636         case cssc::ErrorBarStyle::STANDARD_DEVIATION:
5637             return "stdDev";
5638         case cssc::ErrorBarStyle::ABSOLUTE:
5639             return "fixedVal";
5640         case cssc::ErrorBarStyle::RELATIVE:
5641             return "percentage";
5642         case cssc::ErrorBarStyle::ERROR_MARGIN:
5643             break;
5644         case cssc::ErrorBarStyle::STANDARD_ERROR:
5645             return "stdErr";
5646         case cssc::ErrorBarStyle::FROM_DATA:
5647             return "cust";
5648         default:
5649             assert(false && "can't happen");
5650     }
5651     return nullptr;
5652 }
5653 
getLabeledSequence(const uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence>> & aSequences,bool bPositive)5654 Reference< chart2::data::XDataSequence>  getLabeledSequence(
5655         const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > >& aSequences,
5656         bool bPositive )
5657 {
5658     OUString aDirection;
5659     if(bPositive)
5660         aDirection = "positive";
5661     else
5662         aDirection = "negative";
5663 
5664     for( const auto& rSequence : aSequences )
5665     {
5666         if( rSequence.is())
5667         {
5668             uno::Reference< chart2::data::XDataSequence > xSequence( rSequence->getValues());
5669             uno::Reference< beans::XPropertySet > xSeqProp( xSequence, uno::UNO_QUERY_THROW );
5670             OUString aRole;
5671             if( ( xSeqProp->getPropertyValue( u"Role"_ustr ) >>= aRole ) &&
5672                     aRole.match( "error-bars" ) && aRole.indexOf(aDirection) >= 0 )
5673             {
5674                 return xSequence;
5675             }
5676         }
5677     }
5678 
5679     return Reference< chart2::data::XDataSequence > ();
5680 }
5681 
5682 }
5683 
exportErrorBar(const Reference<XPropertySet> & xErrorBarProps,bool bYError)5684 void ChartExport::exportErrorBar(const Reference< XPropertySet>& xErrorBarProps, bool bYError)
5685 {
5686     sal_Int32 nErrorBarStyle = cssc::ErrorBarStyle::NONE;
5687     xErrorBarProps->getPropertyValue(u"ErrorBarStyle"_ustr) >>= nErrorBarStyle;
5688     const char* pErrorBarStyle = getErrorBarStyle(nErrorBarStyle);
5689     if(!pErrorBarStyle)
5690         return;
5691 
5692     FSHelperPtr pFS = GetFS();
5693     pFS->startElement(FSNS(XML_c, XML_errBars));
5694     pFS->singleElement(FSNS(XML_c, XML_errDir), XML_val, bYError ? "y" : "x");
5695     bool bPositive = false, bNegative = false;
5696     xErrorBarProps->getPropertyValue(u"ShowPositiveError"_ustr) >>= bPositive;
5697     xErrorBarProps->getPropertyValue(u"ShowNegativeError"_ustr) >>= bNegative;
5698     const char* pErrBarType;
5699     if(bPositive && bNegative)
5700         pErrBarType = "both";
5701     else if(bPositive)
5702         pErrBarType = "plus";
5703     else if(bNegative)
5704         pErrBarType = "minus";
5705     else
5706     {
5707         // what the hell should we do now?
5708         // at least this makes the file valid
5709         pErrBarType = "both";
5710     }
5711     pFS->singleElement(FSNS(XML_c, XML_errBarType), XML_val, pErrBarType);
5712     pFS->singleElement(FSNS(XML_c, XML_errValType), XML_val, pErrorBarStyle);
5713     pFS->singleElement(FSNS(XML_c, XML_noEndCap), XML_val, "0");
5714     if(nErrorBarStyle == cssc::ErrorBarStyle::FROM_DATA)
5715     {
5716         uno::Reference< chart2::data::XDataSource > xDataSource(xErrorBarProps, uno::UNO_QUERY);
5717         Sequence< Reference < chart2::data::XLabeledDataSequence > > aSequences =
5718             xDataSource->getDataSequences();
5719 
5720         if(bPositive)
5721         {
5722             exportSeriesValues(getLabeledSequence(aSequences, true), XML_plus);
5723         }
5724 
5725         if(bNegative)
5726         {
5727             exportSeriesValues(getLabeledSequence(aSequences, false), XML_minus);
5728         }
5729     }
5730     else
5731     {
5732         double nVal = 0.0;
5733         if(nErrorBarStyle == cssc::ErrorBarStyle::STANDARD_DEVIATION)
5734         {
5735             xErrorBarProps->getPropertyValue(u"Weight"_ustr) >>= nVal;
5736         }
5737         else
5738         {
5739             if(bPositive)
5740                 xErrorBarProps->getPropertyValue(u"PositiveError"_ustr) >>= nVal;
5741             else
5742                 xErrorBarProps->getPropertyValue(u"NegativeError"_ustr) >>= nVal;
5743         }
5744 
5745         pFS->singleElement(FSNS(XML_c, XML_val), XML_val, OString::number(nVal));
5746     }
5747 
5748     exportShapeProps( xErrorBarProps, false );
5749 
5750     pFS->endElement( FSNS( XML_c, XML_errBars) );
5751 }
5752 
exportView3D()5753 void ChartExport::exportView3D()
5754 {
5755     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
5756     if( !xPropSet.is() )
5757         return;
5758     FSHelperPtr pFS = GetFS();
5759     pFS->startElement(FSNS(XML_c, XML_view3D));
5760     sal_Int32 eChartType = getChartType( );
5761     // rotX
5762     if( GetProperty( xPropSet, u"RotationHorizontal"_ustr ) )
5763     {
5764         sal_Int32 nRotationX = 0;
5765         mAny >>= nRotationX;
5766         if( nRotationX < 0 )
5767         {
5768             if(eChartType == chart::TYPEID_PIE)
5769             {
5770             /* In OOXML we get value in 0..90 range for pie chart X rotation , whereas we expect it to be in -90..90 range,
5771                so we convert that during import. It is modified in View3DConverter::convertFromModel()
5772                here we convert it back to 0..90 as we received in import */
5773                nRotationX += 90;  // X rotation (map Chart2 [-179,180] to OOXML [0..90])
5774             }
5775             else
5776                 nRotationX += 360; // X rotation (map Chart2 [-179,180] to OOXML [-90..90])
5777         }
5778         pFS->singleElement(FSNS(XML_c, XML_rotX), XML_val, OString::number(nRotationX));
5779     }
5780     // rotY
5781     if( GetProperty( xPropSet, u"RotationVertical"_ustr ) )
5782     {
5783         // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
5784         if( eChartType == chart::TYPEID_PIE && GetProperty( xPropSet, u"StartingAngle"_ustr ) )
5785         {
5786          // Y rotation used as 'first pie slice angle' in 3D pie charts
5787             sal_Int32 nStartingAngle=0;
5788             mAny >>= nStartingAngle;
5789             // convert to ooxml angle
5790             nStartingAngle = (450 - nStartingAngle ) % 360;
5791             pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nStartingAngle));
5792         }
5793         else
5794         {
5795             sal_Int32 nRotationY = 0;
5796             mAny >>= nRotationY;
5797             // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
5798             if( nRotationY < 0 )
5799                 nRotationY += 360;
5800             pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nRotationY));
5801         }
5802     }
5803     // rAngAx
5804     if( GetProperty( xPropSet, u"RightAngledAxes"_ustr ) )
5805     {
5806         bool bRightAngled = false;
5807         mAny >>= bRightAngled;
5808         const char* sRightAngled = bRightAngled ? "1":"0";
5809         pFS->singleElement(FSNS(XML_c, XML_rAngAx), XML_val, sRightAngled);
5810     }
5811     // perspective
5812     if( GetProperty( xPropSet, u"Perspective"_ustr ) )
5813     {
5814         sal_Int32 nPerspective = 0;
5815         mAny >>= nPerspective;
5816         // map Chart2 [0,100] to OOXML [0..200]
5817         nPerspective *= 2;
5818         pFS->singleElement(FSNS(XML_c, XML_perspective), XML_val, OString::number(nPerspective));
5819     }
5820     pFS->endElement( FSNS( XML_c, XML_view3D ) );
5821 }
5822 
isDeep3dChart()5823 bool ChartExport::isDeep3dChart()
5824 {
5825     bool isDeep = false;
5826     if( mbIs3DChart )
5827     {
5828         Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
5829         if( GetProperty( xPropSet, u"Deep"_ustr ) )
5830             mAny >>= isDeep;
5831     }
5832     return isDeep;
5833 }
5834 
isChartexNotChartNS() const5835 bool ChartExport::isChartexNotChartNS() const
5836 {
5837     Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, uno::UNO_QUERY );
5838     if( ! xBCooSysCnt.is()) return false;
5839 
5840     // chart type
5841     const Sequence< Reference< chart2::XCoordinateSystem > >
5842         aCooSysSeq( xBCooSysCnt->getCoordinateSystems());
5843 
5844     for( const auto& rCS : aCooSysSeq ) {
5845         Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY );
5846         if( ! xCTCnt.is())
5847             continue;
5848         const Sequence< Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes());
5849         for( const auto& rCT : aCTSeq ) {
5850             Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, uno::UNO_QUERY );
5851             if( ! xDSCnt.is())
5852                 return false;
5853             Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY );
5854             if( ! xChartType.is())
5855                 continue;
5856             // note: if xDSCnt.is() then also aCTSeq[nCTIdx]
5857             OUString aChartType( xChartType->getChartType());
5858             sal_Int32 eChartType = lcl_getChartType( aChartType );
5859             switch( eChartType )
5860             {
5861                 case chart::TYPEID_BAR:
5862                 case chart::TYPEID_AREA:
5863                 case chart::TYPEID_LINE:
5864                 case chart::TYPEID_BUBBLE:
5865                 case chart::TYPEID_OFPIE:
5866                 case chart::TYPEID_DOUGHNUT:
5867                 case chart::TYPEID_PIE:
5868                 case chart::TYPEID_RADARLINE:
5869                 case chart::TYPEID_RADARAREA:
5870                 case chart::TYPEID_SCATTER:
5871                 case chart::TYPEID_STOCK:
5872                 case chart::TYPEID_SURFACE:
5873                     break;
5874                 case chart::TYPEID_FUNNEL:
5875                     return true;
5876                 default:
5877                     assert(false);
5878                     break;
5879             }
5880         }
5881     }
5882     return false;
5883 }
5884 
getNumberFormatCode(sal_Int32 nKey) const5885 OUString ChartExport::getNumberFormatCode(sal_Int32 nKey) const
5886 {
5887     /* XXX if this was called more than one or two times per export the two
5888      * SvNumberFormatter instances and NfKeywordTable should be member
5889      * variables and initialized only once. */
5890 
5891     OUString aCode(u"General"_ustr);  // init with fallback
5892     uno::Reference<util::XNumberFormatsSupplier> xNumberFormatsSupplier(mxChartModel, uno::UNO_QUERY_THROW);
5893     SvNumberFormatsSupplierObj* pSupplierObj = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( xNumberFormatsSupplier);
5894     if (!pSupplierObj)
5895         return aCode;
5896 
5897     SvNumberFormatter* pNumberFormatter = pSupplierObj->GetNumberFormatter();
5898     if (!pNumberFormatter)
5899         return aCode;
5900 
5901     SvNumberFormatter aTempFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US);
5902     NfKeywordTable aKeywords;
5903     aTempFormatter.FillKeywordTableForExcel( aKeywords);
5904     aCode = pNumberFormatter->GetFormatStringForExcel( nKey, aKeywords, aTempFormatter);
5905 
5906     return aCode;
5907 }
5908 
5909 }// oox
5910 
5911 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
5912