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/tokens.hxx>
22 #include <oox/core/xmlfilterbase.hxx>
23 #include <oox/export/chartexport.hxx>
24 #include <oox/token/relationship.hxx>
25 #include <oox/export/utils.hxx>
26 #include <drawingml/chart/typegroupconverter.hxx>
27 
28 #include <cstdio>
29 #include <iterator>
30 
31 #include <com/sun/star/awt/Gradient.hpp>
32 #include <com/sun/star/chart/XChartDocument.hpp>
33 #include <com/sun/star/chart/ChartLegendPosition.hpp>
34 #include <com/sun/star/chart/XTwoAxisXSupplier.hpp>
35 #include <com/sun/star/chart/XTwoAxisYSupplier.hpp>
36 #include <com/sun/star/chart/XAxisZSupplier.hpp>
37 #include <com/sun/star/chart/XChartDataArray.hpp>
38 #include <com/sun/star/chart/ChartDataRowSource.hpp>
39 #include <com/sun/star/chart/ChartAxisAssign.hpp>
40 #include <com/sun/star/chart/ChartSeriesAddress.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 
54 #include <com/sun/star/chart2/RelativePosition.hpp>
55 #include <com/sun/star/chart2/RelativeSize.hpp>
56 #include <com/sun/star/chart2/XChartDocument.hpp>
57 #include <com/sun/star/chart2/XDiagram.hpp>
58 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
59 #include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
60 #include <com/sun/star/chart2/XChartTypeContainer.hpp>
61 #include <com/sun/star/chart2/XDataSeriesContainer.hpp>
62 #include <com/sun/star/chart2/DataPointGeometry3D.hpp>
63 #include <com/sun/star/chart2/DataPointLabel.hpp>
64 #include <com/sun/star/chart2/DataPointCustomLabelField.hpp>
65 #include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp>
66 #include <com/sun/star/chart2/Symbol.hpp>
67 #include <com/sun/star/chart2/data/XDataSource.hpp>
68 #include <com/sun/star/chart2/data/XDataSink.hpp>
69 #include <com/sun/star/chart2/data/XDataReceiver.hpp>
70 #include <com/sun/star/chart2/data/XDataProvider.hpp>
71 #include <com/sun/star/chart2/XInternalDataProvider.hpp>
72 #include <com/sun/star/chart2/data/XDatabaseDataProvider.hpp>
73 #include <com/sun/star/chart2/data/XRangeXMLConversion.hpp>
74 #include <com/sun/star/chart2/data/XTextualDataSequence.hpp>
75 #include <com/sun/star/chart2/data/XNumericalDataSequence.hpp>
76 #include <com/sun/star/chart2/data/XLabeledDataSequence.hpp>
77 #include <com/sun/star/chart2/XAnyDescriptionAccess.hpp>
78 
79 #include <com/sun/star/beans/XPropertySet.hpp>
80 #include <com/sun/star/drawing/XShape.hpp>
81 #include <com/sun/star/drawing/FillStyle.hpp>
82 #include <com/sun/star/drawing/LineStyle.hpp>
83 #include <com/sun/star/drawing/BitmapMode.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/text/WritingMode.hpp>
94 #include <com/sun/star/container/XNamed.hpp>
95 #include <com/sun/star/embed/XVisualObject.hpp>
96 #include <com/sun/star/embed/Aspects.hpp>
97 
98 #include <comphelper/processfactory.hxx>
99 #include <comphelper/random.hxx>
100 #include <utility>
101 #include <xmloff/SchXMLSeriesHelper.hxx>
102 #include "ColorPropertySet.hxx"
103 
104 #include <svl/zforlist.hxx>
105 #include <svl/numuno.hxx>
106 #include <tools/diagnose_ex.h>
107 #include <sal/log.hxx>
108 
109 #include <set>
110 #include <unordered_set>
111 
112 #include <rtl/math.hxx>
113 #include <o3tl/temporary.hxx>
114 
115 using namespace css;
116 using namespace css::uno;
117 using namespace css::drawing;
118 using namespace ::oox::core;
119 using css::beans::PropertyValue;
120 using css::beans::XPropertySet;
121 using css::container::XNamed;
122 using css::table::CellAddress;
123 using css::sheet::XFormulaParser;
124 using ::oox::core::XmlFilterBase;
125 using ::sax_fastparser::FSHelperPtr;
126 
127 namespace cssc = css::chart;
128 
129 namespace oox { namespace drawingml {
130 
131 namespace {
132 
isPrimaryAxes(sal_Int32 nIndex)133 bool isPrimaryAxes(sal_Int32 nIndex)
134 {
135     assert(nIndex == 0 || nIndex == 1);
136     return nIndex != 1;
137 }
138 
139 }
140 
141 class lcl_MatchesRole
142 {
143 public:
lcl_MatchesRole(const OUString & aRole)144     explicit lcl_MatchesRole( const OUString & aRole ) :
145             m_aRole( aRole )
146     {}
147 
operator ()(const Reference<chart2::data::XLabeledDataSequence> & xSeq) const148     bool operator () ( const Reference< chart2::data::XLabeledDataSequence > & xSeq ) const
149     {
150         if( !xSeq.is() )
151             return  false;
152         Reference< beans::XPropertySet > xProp( xSeq->getValues(), uno::UNO_QUERY );
153         OUString aRole;
154 
155         return ( xProp.is() &&
156                  (xProp->getPropertyValue( "Role" ) >>= aRole ) &&
157                  m_aRole == aRole );
158     }
159 
160 private:
161     OUString const m_aRole;
162 };
163 
lcl_getCategories(const Reference<chart2::XDiagram> & xDiagram)164 static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories( const Reference< chart2::XDiagram > & xDiagram )
165 {
166     Reference< chart2::data::XLabeledDataSequence >  xResult;
167     try
168     {
169         Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
170             xDiagram, uno::UNO_QUERY_THROW );
171         const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
172             xCooSysCnt->getCoordinateSystems());
173         for( const auto& xCooSys : aCooSysSeq )
174         {
175             OSL_ASSERT( xCooSys.is());
176             for( sal_Int32 nN = xCooSys->getDimension(); nN--; )
177             {
178                 const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nN);
179                 for(sal_Int32 nI=0; nI<=nMaxAxisIndex; ++nI)
180                 {
181                     Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension( nN, nI );
182                     OSL_ASSERT( xAxis.is());
183                     if( xAxis.is())
184                     {
185                         chart2::ScaleData aScaleData = xAxis->getScaleData();
186                         if( aScaleData.Categories.is())
187                         {
188                             xResult.set( aScaleData.Categories );
189                             break;
190                         }
191                     }
192                 }
193             }
194         }
195     }
196     catch( const uno::Exception & )
197     {
198         DBG_UNHANDLED_EXCEPTION("oox");
199     }
200 
201     return xResult;
202 }
203 
204 static Reference< chart2::data::XLabeledDataSequence >
lcl_getDataSequenceByRole(const Sequence<Reference<chart2::data::XLabeledDataSequence>> & aLabeledSeq,const OUString & rRole)205     lcl_getDataSequenceByRole(
206         const Sequence< Reference< chart2::data::XLabeledDataSequence > > & aLabeledSeq,
207         const OUString & rRole )
208 {
209     Reference< chart2::data::XLabeledDataSequence > aNoResult;
210 
211     const Reference< chart2::data::XLabeledDataSequence > * pBegin = aLabeledSeq.getConstArray();
212     const Reference< chart2::data::XLabeledDataSequence > * pEnd = pBegin + aLabeledSeq.getLength();
213     const Reference< chart2::data::XLabeledDataSequence > * pMatch =
214         ::std::find_if( pBegin, pEnd, lcl_MatchesRole( rRole ));
215 
216     if( pMatch != pEnd )
217         return *pMatch;
218 
219     return aNoResult;
220 }
221 
lcl_hasCategoryLabels(const Reference<chart2::XChartDocument> & xChartDoc)222 static bool lcl_hasCategoryLabels( const Reference< chart2::XChartDocument >& xChartDoc )
223 {
224     //categories are always the first sequence
225     Reference< chart2::XDiagram > xDiagram( xChartDoc->getFirstDiagram());
226     Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( xDiagram ) );
227     return xCategories.is();
228 }
229 
lcl_isCategoryAxisShifted(const Reference<chart2::XChartDocument> & xChartDoc)230 static bool lcl_isCategoryAxisShifted(const Reference< chart2::XChartDocument >& xChartDoc)
231 {
232     Reference< chart2::XDiagram > xDiagram(xChartDoc->getFirstDiagram());
233     bool isCategoryPositionShifted = false;
234 
235     try
236     {
237         Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
238             xDiagram, uno::UNO_QUERY_THROW);
239         const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
240             xCooSysCnt->getCoordinateSystems());
241         for( const auto& xCooSys : aCooSysSeq )
242         {
243             OSL_ASSERT(xCooSys.is());
244             for( sal_Int32 nN = xCooSys->getDimension(); nN--; )
245             {
246                 const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nN);
247                 for( sal_Int32 nI = 0; nI <= nMaxAxisIndex; ++nI )
248                 {
249                     Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(nN, nI);
250                     OSL_ASSERT(xAxis.is());
251                     if( xAxis.is())
252                     {
253                         chart2::ScaleData aScaleData = xAxis->getScaleData();
254                         if( aScaleData.AxisType == AXIS_PRIMARY_Y )
255                         {
256                             isCategoryPositionShifted = aScaleData.ShiftedCategoryPosition;
257                             break;
258                         }
259                     }
260                 }
261             }
262         }
263     }
264     catch (const uno::Exception &)
265     {
266         DBG_UNHANDLED_EXCEPTION("oox");
267     }
268 
269     return isCategoryPositionShifted;
270 }
271 
lcl_isSeriesAttachedToFirstAxis(const Reference<chart2::XDataSeries> & xDataSeries)272 static bool lcl_isSeriesAttachedToFirstAxis(
273     const Reference< chart2::XDataSeries > & xDataSeries )
274 {
275     bool bResult=true;
276 
277     try
278     {
279         sal_Int32 nAxisIndex = 0;
280         Reference< beans::XPropertySet > xProp( xDataSeries, uno::UNO_QUERY_THROW );
281         xProp->getPropertyValue("AttachedAxisIndex") >>= nAxisIndex;
282         bResult = (0==nAxisIndex);
283     }
284     catch( const uno::Exception & )
285     {
286         DBG_UNHANDLED_EXCEPTION("oox");
287     }
288 
289     return bResult;
290 }
291 
lcl_flattenStringSequence(const Sequence<OUString> & rSequence)292 static OUString lcl_flattenStringSequence( const Sequence< OUString > & rSequence )
293 {
294     OUStringBuffer aResult;
295     bool bPrecedeWithSpace = false;
296     for( const auto& rString : rSequence )
297     {
298         if( !rString.isEmpty())
299         {
300             if( bPrecedeWithSpace )
301                 aResult.append( ' ' );
302             aResult.append( rString );
303             bPrecedeWithSpace = true;
304         }
305     }
306     return aResult.makeStringAndClear();
307 }
308 
lcl_getLabelSequence(const Reference<chart2::data::XDataSequence> & xLabelSeq)309 static Sequence< OUString > lcl_getLabelSequence( const Reference< chart2::data::XDataSequence > & xLabelSeq )
310 {
311     Sequence< OUString > aLabels;
312 
313     uno::Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xLabelSeq, uno::UNO_QUERY );
314     if( xTextualDataSequence.is())
315     {
316         aLabels = xTextualDataSequence->getTextualData();
317     }
318     else if( xLabelSeq.is())
319     {
320         const Sequence< uno::Any > aAnies( xLabelSeq->getData());
321         aLabels.realloc( aAnies.getLength());
322         for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
323             aAnies[i] >>= aLabels[i];
324     }
325 
326     return aLabels;
327 }
328 
lcl_fillCategoriesIntoStringVector(const Reference<chart2::data::XDataSequence> & xCategories,::std::vector<OUString> & rOutCategories)329 static void lcl_fillCategoriesIntoStringVector(
330     const Reference< chart2::data::XDataSequence > & xCategories,
331     ::std::vector< OUString > & rOutCategories )
332 {
333     OSL_ASSERT( xCategories.is());
334     if( !xCategories.is())
335         return;
336     Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xCategories, uno::UNO_QUERY );
337     if( xTextualDataSequence.is())
338     {
339         rOutCategories.clear();
340         Sequence< OUString > aTextData( xTextualDataSequence->getTextualData());
341         ::std::copy( aTextData.begin(), aTextData.end(),
342                      ::std::back_inserter( rOutCategories ));
343     }
344     else
345     {
346         Sequence< uno::Any > aAnies( xCategories->getData());
347         rOutCategories.resize( aAnies.getLength());
348         for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
349             aAnies[i] >>= rOutCategories[i];
350     }
351 }
352 
lcl_getAllValuesFromSequence(const Reference<chart2::data::XDataSequence> & xSeq)353 static ::std::vector< double > lcl_getAllValuesFromSequence( const Reference< chart2::data::XDataSequence > & xSeq )
354 {
355     double fNan = 0.0;
356     ::rtl::math::setNan( &fNan );
357     ::std::vector< double > aResult;
358 
359     Reference< chart2::data::XNumericalDataSequence > xNumSeq( xSeq, uno::UNO_QUERY );
360     if( xNumSeq.is())
361     {
362         Sequence< double > aValues( xNumSeq->getNumericalData());
363         ::std::copy( aValues.begin(), aValues.end(),
364                      ::std::back_inserter( aResult ));
365     }
366     else if( xSeq.is())
367     {
368         Sequence< uno::Any > aAnies( xSeq->getData());
369         aResult.resize( aAnies.getLength(), fNan );
370         for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
371             aAnies[i] >>= aResult[i];
372     }
373     return aResult;
374 }
375 
lcl_getChartType(const OUString & sChartType)376 static sal_Int32 lcl_getChartType( const OUString& sChartType )
377 {
378     chart::TypeId eChartTypeId = chart::TYPEID_UNKNOWN;
379     if( sChartType == "com.sun.star.chart.BarDiagram"
380         || sChartType == "com.sun.star.chart2.ColumnChartType" )
381         eChartTypeId = chart::TYPEID_BAR;
382     else if( sChartType == "com.sun.star.chart.AreaDiagram"
383              || sChartType == "com.sun.star.chart2.AreaChartType" )
384         eChartTypeId = chart::TYPEID_AREA;
385     else if( sChartType == "com.sun.star.chart.LineDiagram"
386              || sChartType == "com.sun.star.chart2.LineChartType" )
387         eChartTypeId = chart::TYPEID_LINE;
388     else if( sChartType == "com.sun.star.chart.PieDiagram"
389              || sChartType == "com.sun.star.chart2.PieChartType" )
390         eChartTypeId = chart::TYPEID_PIE;
391     else if( sChartType == "com.sun.star.chart.DonutDiagram"
392              || sChartType == "com.sun.star.chart2.DonutChartType" )
393         eChartTypeId = chart::TYPEID_DOUGHNUT;
394     else if( sChartType == "com.sun.star.chart.XYDiagram"
395              || sChartType == "com.sun.star.chart2.ScatterChartType" )
396         eChartTypeId = chart::TYPEID_SCATTER;
397     else if( sChartType == "com.sun.star.chart.NetDiagram"
398              || sChartType == "com.sun.star.chart2.NetChartType" )
399         eChartTypeId = chart::TYPEID_RADARLINE;
400     else if( sChartType == "com.sun.star.chart.FilledNetDiagram"
401              || sChartType == "com.sun.star.chart2.FilledNetChartType" )
402         eChartTypeId = chart::TYPEID_RADARAREA;
403     else if( sChartType == "com.sun.star.chart.StockDiagram"
404              || sChartType == "com.sun.star.chart2.CandleStickChartType" )
405         eChartTypeId = chart::TYPEID_STOCK;
406     else if( sChartType == "com.sun.star.chart.BubbleDiagram"
407              || sChartType == "com.sun.star.chart2.BubbleChartType" )
408         eChartTypeId = chart::TYPEID_BUBBLE;
409 
410     return eChartTypeId;
411 }
412 
lcl_generateRandomValue()413 static sal_Int32 lcl_generateRandomValue()
414 {
415     return comphelper::rng::uniform_int_distribution(0, 100000000-1);
416 }
417 
ChartExport(sal_Int32 nXmlNamespace,FSHelperPtr pFS,Reference<frame::XModel> const & xModel,XmlFilterBase * pFB,DocumentType eDocumentType)418 ChartExport::ChartExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, Reference< frame::XModel > const & xModel, XmlFilterBase* pFB, DocumentType eDocumentType )
419     : DrawingML( std::move(pFS), pFB, eDocumentType )
420     , mnXmlNamespace( nXmlNamespace )
421     , mnSeriesCount(0)
422     , mxChartModel( xModel )
423     , mpURLTransformer(new URLTransformer)
424     , mbHasCategoryLabels( false )
425     , mbIsCategoryPositionShifted( false )
426     , mbHasZAxis( false )
427     , mbIs3DChart( false )
428     , mbStacked(false)
429     , mbPercent(false)
430 {
431 }
432 
SetURLTranslator(const std::shared_ptr<URLTransformer> & pTransformer)433 void ChartExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer)
434 {
435     mpURLTransformer = pTransformer;
436 }
437 
getChartType()438 sal_Int32 ChartExport::getChartType( )
439 {
440     OUString sChartType = mxDiagram->getDiagramType();
441     return lcl_getChartType( sChartType );
442 }
443 
444 namespace {
445 
createArguments(const OUString & rRangeRepresentation,bool bUseColumns)446 uno::Sequence< beans::PropertyValue > createArguments(
447     const OUString & rRangeRepresentation, bool bUseColumns)
448 {
449     css::chart::ChartDataRowSource eRowSource = css::chart::ChartDataRowSource_ROWS;
450     if (bUseColumns)
451         eRowSource = css::chart::ChartDataRowSource_COLUMNS;
452 
453     uno::Sequence< beans::PropertyValue > aArguments(4);
454     aArguments[0] = beans::PropertyValue("DataRowSource"
455         , -1, uno::Any(eRowSource)
456         , beans::PropertyState_DIRECT_VALUE);
457     aArguments[1] = beans::PropertyValue("FirstCellAsLabel"
458         , -1, uno::Any(false)
459         , beans::PropertyState_DIRECT_VALUE);
460     aArguments[2] = beans::PropertyValue("HasCategories"
461         , -1, uno::Any(false)
462         , beans::PropertyState_DIRECT_VALUE);
463     aArguments[3] = beans::PropertyValue("CellRangeRepresentation"
464         , -1, uno::Any(rRangeRepresentation)
465         , beans::PropertyState_DIRECT_VALUE);
466 
467     return aArguments;
468 }
469 
getPrimaryDataSeries(const Reference<chart2::XChartType> & xChartType)470 Reference<chart2::XDataSeries> getPrimaryDataSeries(const Reference<chart2::XChartType>& xChartType)
471 {
472     Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY_THROW);
473 
474     // export dataseries for current chart-type
475     const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
476     for (const auto& rSeries : aSeriesSeq)
477     {
478         Reference<chart2::XDataSeries> xSource(rSeries, uno::UNO_QUERY);
479         if (xSource.is())
480             return xSource;
481     }
482 
483     return Reference<chart2::XDataSeries>();
484 }
485 
486 }
487 
getSplitCategoriesList(const OUString & rRange)488 Sequence< Sequence< OUString > > ChartExport::getSplitCategoriesList( const OUString& rRange )
489 {
490     Reference< chart2::XChartDocument > xChartDoc(getModel(), uno::UNO_QUERY);
491     OSL_ASSERT(xChartDoc.is());
492     if (xChartDoc.is())
493     {
494         Reference< chart2::data::XDataProvider > xDataProvider(xChartDoc->getDataProvider());
495         OSL_ENSURE(xDataProvider.is(), "No DataProvider");
496         if (xDataProvider.is())
497         {
498             //detect whether the first series is a row or a column
499             bool bSeriesUsesColumns = true;
500             Reference< chart2::XDiagram > xDiagram(xChartDoc->getFirstDiagram());
501             try
502             {
503                 Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(xDiagram, uno::UNO_QUERY_THROW);
504                 const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(xCooSysCnt->getCoordinateSystems());
505                 for (const auto& rCooSys : aCooSysSeq)
506                 {
507                     const Reference< chart2::XChartTypeContainer > xCTCnt(rCooSys, uno::UNO_QUERY_THROW);
508                     const Sequence< Reference< chart2::XChartType > > aChartTypeSeq(xCTCnt->getChartTypes());
509                     for (const auto& rChartType : aChartTypeSeq)
510                     {
511                         Reference< chart2::XDataSeries > xDataSeries = getPrimaryDataSeries(rChartType);
512                         if (xDataSeries.is())
513                         {
514                             uno::Reference< chart2::data::XDataSource > xSeriesSource(xDataSeries, uno::UNO_QUERY);
515                             const uno::Sequence< beans::PropertyValue > rArguments = xDataProvider->detectArguments(xSeriesSource);
516                             for (const beans::PropertyValue& rProperty : rArguments)
517                             {
518                                 if (rProperty.Name == "DataRowSource")
519                                 {
520                                     css::chart::ChartDataRowSource eRowSource;
521                                     if (rProperty.Value >>= eRowSource)
522                                     {
523                                         bSeriesUsesColumns = (eRowSource == css::chart::ChartDataRowSource_COLUMNS);
524                                         break;
525                                     }
526                                 }
527                             }
528                         }
529                     }
530                 }
531             }
532             catch (const uno::Exception &)
533             {
534                 DBG_UNHANDLED_EXCEPTION("chart2");
535             }
536             // detect we have an inner data table or not
537             if (xChartDoc->hasInternalDataProvider() && rRange == "categories")
538             {
539                 try
540                 {
541                     css::uno::Reference< css::chart2::XAnyDescriptionAccess > xDataAccess(xChartDoc->getDataProvider(), uno::UNO_QUERY);
542                     const Sequence< Sequence< uno::Any > >aAnyCategories(bSeriesUsesColumns ? xDataAccess->getAnyRowDescriptions() : xDataAccess->getAnyColumnDescriptions());
543                     auto pMax = std::max_element(aAnyCategories.begin(), aAnyCategories.end(),
544                         [](const Sequence<uno::Any>& a, const Sequence<uno::Any>& b) {
545                             return a.getLength() < b.getLength(); });
546 
547                     //minimum is 1!
548                     if (pMax != aAnyCategories.end() && pMax->getLength() > 1)
549                     {
550                         sal_Int32 nLevelCount = pMax->getLength();
551                         //we have complex categories
552                         //sort the categories name
553                         Sequence<Sequence<OUString>>aFinalSplitSource(nLevelCount);
554                         for (sal_Int32 i = 0; i < nLevelCount; i++)
555                         {
556                             sal_Int32 nElemLabel = 0;
557                             aFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength());
558                             for (auto const& elemLabel : aAnyCategories)
559                             {
560                                 // make sure elemLabel[i] exists!
561                                 if (elemLabel.getLength() > i)
562                                 {
563                                     aFinalSplitSource[nLevelCount - i - 1][nElemLabel] = elemLabel[i].get<OUString>();
564                                     nElemLabel++;
565                                 }
566                             }
567                         }
568                         return aFinalSplitSource;
569                     }
570                 }
571                 catch (const uno::Exception &)
572                 {
573                     DBG_UNHANDLED_EXCEPTION("oox");
574                 }
575             }
576             else
577             {
578                 try
579                 {
580                     uno::Reference< chart2::data::XDataSource > xCategoriesSource(xDataProvider->createDataSource(
581                         createArguments(rRange, bSeriesUsesColumns)));
582 
583                     if (xCategoriesSource.is())
584                     {
585                         const Sequence< Reference< chart2::data::XLabeledDataSequence >> aCategories = xCategoriesSource->getDataSequences();
586                         if (aCategories.getLength() > 1)
587                         {
588                             //we have complex categories
589                             //sort the categories name
590                             Sequence<Sequence<OUString>> aFinalSplitSource(aCategories.getLength());
591                             std::transform(aCategories.begin(), aCategories.end(),
592                                 std::reverse_iterator(aFinalSplitSource.end()),
593                                 [](const Reference<chart2::data::XLabeledDataSequence>& xCat) {
594                                     return lcl_getLabelSequence(xCat->getValues()); });
595                             return aFinalSplitSource;
596                         }
597                     }
598                 }
599                 catch (const uno::Exception &)
600                 {
601                     DBG_UNHANDLED_EXCEPTION("oox");
602                 }
603             }
604         }
605     }
606 
607     return Sequence< Sequence< OUString>>(0);
608 }
609 
parseFormula(const OUString & rRange)610 OUString ChartExport::parseFormula( const OUString& rRange )
611 {
612     OUString aResult;
613     Reference< XFormulaParser > xParser;
614     uno::Reference< lang::XMultiServiceFactory > xSF = GetFB()->getModelFactory();
615     if( xSF.is() )
616     {
617         try
618         {
619             xParser.set( xSF->createInstance("com.sun.star.sheet.FormulaParser"), UNO_QUERY );
620         }
621         catch( Exception& )
622         {
623         }
624     }
625 
626     SAL_WARN_IF(!xParser.is(), "oox", "creating formula parser failed");
627 
628     if( xParser.is() )
629     {
630         Reference< XPropertySet > xParserProps( xParser, uno::UNO_QUERY );
631         // rRange is the result of a
632         // css::chart2::data::XDataSequence::getSourceRangeRepresentation()
633         // call that returns the range in the document's current UI notation.
634         // Creating a FormulaParser defaults to the same notation, for
635         // parseFormula() do not attempt to override the FormulaConvention
636         // property with css::sheet::AddressConvention::OOO or some such.
637         /* TODO: it would be much better to introduce a
638          * getSourceRangeRepresentation(css::sheet::AddressConvention) to
639          * return the ranges in a specific convention than converting them with
640          * the overhead of creating an XFormulaParser for each... */
641         uno::Sequence<sheet::FormulaToken> aTokens = xParser->parseFormula( rRange, CellAddress( 0, 0, 0 ) );
642         if( xParserProps.is() )
643         {
644             xParserProps->setPropertyValue("FormulaConvention", uno::makeAny(css::sheet::AddressConvention::XL_OOX) );
645         }
646         aResult = xParser->printFormula( aTokens, CellAddress( 0, 0, 0 ) );
647     }
648     else
649     {
650         //FIXME: currently just using simple converter, e.g $Sheet1.$A$1:$C$1 -> Sheet1!$A$1:$C$1
651         OUString aRange( rRange );
652         if( aRange.startsWith("$") )
653             aRange = aRange.copy(1);
654         aRange = aRange.replaceAll(".$", "!$" );
655         aResult = aRange;
656     }
657 
658     return aResult;
659 }
660 
WriteChartObj(const Reference<XShape> & xShape,sal_Int32 nID,sal_Int32 nChartCount)661 void ChartExport::WriteChartObj( const Reference< XShape >& xShape, sal_Int32 nID, sal_Int32 nChartCount )
662 {
663     FSHelperPtr pFS = GetFS();
664 
665     Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
666 
667     pFS->startElementNS(mnXmlNamespace, XML_graphicFrame);
668 
669     pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);
670 
671     // TODO: get the correct chart name chart id
672     OUString sName = "Object 1";
673     Reference< XNamed > xNamed( xShape, UNO_QUERY );
674     if (xNamed.is())
675         sName = xNamed->getName();
676 
677     pFS->startElementNS( mnXmlNamespace, XML_cNvPr,
678                           XML_id,     OString::number(nID),
679                           XML_name,   sName.toUtf8());
680 
681     OUString sURL;
682     if ( GetProperty( xShapeProps, "URL" ) )
683         mAny >>= sURL;
684     if( !sURL.isEmpty() )
685     {
686         OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
687                 oox::getRelationship(Relationship::HYPERLINK),
688                 mpURLTransformer->getTransformedString(sURL),
689                 mpURLTransformer->isExternalURL(sURL));
690 
691         mpFS->singleElementNS( XML_a, XML_hlinkClick,
692                 FSNS( XML_r,XML_id ), sRelId.toUtf8() );
693     }
694     pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
695 
696     pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);
697 
698     if( GetDocumentType() == DOCUMENT_PPTX )
699         pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
700     pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );
701 
702     // visual chart properties
703     WriteShapeTransformation( xShape, mnXmlNamespace );
704 
705     // writer chart object
706     pFS->startElement(FSNS(XML_a, XML_graphic));
707     pFS->startElement( FSNS( XML_a, XML_graphicData ),
708                        XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" );
709     OUString sId;
710     const char* sFullPath = nullptr;
711     const char* sRelativePath = nullptr;
712     switch( GetDocumentType() )
713     {
714         case DOCUMENT_DOCX:
715         {
716             sFullPath = "word/charts/chart";
717             sRelativePath = "charts/chart";
718             break;
719         }
720         case DOCUMENT_PPTX:
721         {
722             sFullPath = "ppt/charts/chart";
723             sRelativePath = "../charts/chart";
724             break;
725         }
726         case DOCUMENT_XLSX:
727         {
728             sFullPath = "xl/charts/chart";
729             sRelativePath = "../charts/chart";
730             break;
731         }
732         default:
733         {
734             sFullPath = "charts/chart";
735             sRelativePath = "charts/chart";
736             break;
737         }
738     }
739     OUString sFullStream = OUStringBuffer()
740                             .appendAscii(sFullPath)
741                             .append(nChartCount)
742                             .append( ".xml" )
743                             .makeStringAndClear();
744     OUString sRelativeStream = OUStringBuffer()
745                             .appendAscii(sRelativePath)
746                             .append(nChartCount)
747                             .append( ".xml" )
748                             .makeStringAndClear();
749     FSHelperPtr pChart = CreateOutputStream(
750             sFullStream,
751             sRelativeStream,
752             pFS->getOutputStream(),
753             "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
754             OUStringToOString(oox::getRelationship(Relationship::CHART), RTL_TEXTENCODING_UTF8).getStr(),
755             &sId );
756 
757     XmlFilterBase* pFB = GetFB();
758     pFS->singleElement(  FSNS( XML_c, XML_chart ),
759             FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)).toUtf8(),
760             FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)).toUtf8(),
761             FSNS(XML_r, XML_id), sId.toUtf8() );
762 
763     pFS->endElement( FSNS( XML_a, XML_graphicData ) );
764     pFS->endElement( FSNS( XML_a, XML_graphic ) );
765     pFS->endElementNS( mnXmlNamespace, XML_graphicFrame );
766 
767     SetFS( pChart );
768     ExportContent();
769 }
770 
InitRangeSegmentationProperties(const Reference<chart2::XChartDocument> & xChartDoc)771 void ChartExport::InitRangeSegmentationProperties( const Reference< chart2::XChartDocument > & xChartDoc )
772 {
773     if( xChartDoc.is())
774         try
775         {
776             Reference< chart2::data::XDataProvider > xDataProvider( xChartDoc->getDataProvider() );
777             OSL_ENSURE( xDataProvider.is(), "No DataProvider" );
778             if( xDataProvider.is())
779             {
780                 mbHasCategoryLabels = lcl_hasCategoryLabels( xChartDoc );
781                 mbIsCategoryPositionShifted = lcl_isCategoryAxisShifted( xChartDoc );
782             }
783         }
784         catch( const uno::Exception & )
785         {
786             DBG_UNHANDLED_EXCEPTION("oox");
787         }
788 }
789 
ExportContent()790 void ChartExport::ExportContent()
791 {
792     Reference< chart2::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
793     OSL_ASSERT( xChartDoc.is() );
794     if( !xChartDoc.is() )
795         return;
796     InitRangeSegmentationProperties( xChartDoc );
797     // TODO: export chart
798     ExportContent_( );
799 }
800 
ExportContent_()801 void ChartExport::ExportContent_()
802 {
803     Reference< css::chart::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
804     if( xChartDoc.is())
805     {
806         // determine if data comes from the outside
807         bool bIncludeTable = true;
808 
809         Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
810         if( xNewDoc.is())
811         {
812             // check if we have own data.  If so we must not export the complete
813             // range string, as this is our only indicator for having own or
814             // external data. @todo: fix this in the file format!
815             Reference< lang::XServiceInfo > xDPServiceInfo( xNewDoc->getDataProvider(), uno::UNO_QUERY );
816             if( ! (xDPServiceInfo.is() && xDPServiceInfo->getImplementationName() == "com.sun.star.comp.chart.InternalDataProvider" ))
817             {
818                 bIncludeTable = false;
819             }
820         }
821         exportChartSpace( xChartDoc, bIncludeTable );
822     }
823     else
824     {
825         OSL_FAIL( "Couldn't export chart due to wrong XModel" );
826     }
827 }
828 
exportChartSpace(const Reference<css::chart::XChartDocument> & xChartDoc,bool bIncludeTable)829 void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument >& xChartDoc,
830                                     bool bIncludeTable )
831 {
832     FSHelperPtr pFS = GetFS();
833     XmlFilterBase* pFB = GetFB();
834     pFS->startElement( FSNS( XML_c, XML_chartSpace ),
835             FSNS( XML_xmlns, XML_c ), pFB->getNamespaceURL(OOX_NS(dmlChart)).toUtf8(),
836             FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)).toUtf8(),
837             FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel)).toUtf8());
838     // TODO: get the correct editing language
839     pFS->singleElement(FSNS(XML_c, XML_lang), XML_val, "en-US");
840 
841     pFS->singleElement(FSNS(XML_c, XML_roundedCorners), XML_val, "0");
842 
843     if( !bIncludeTable )
844     {
845         // TODO:external data
846     }
847     //XML_chart
848     exportChart(xChartDoc);
849 
850     // TODO: printSettings
851     // TODO: style
852     // TODO: text properties
853     // TODO: shape properties
854     Reference< XPropertySet > xPropSet = xChartDoc->getArea();
855     if( xPropSet.is() )
856         exportShapeProps( xPropSet );
857 
858     //XML_externalData
859     exportExternalData(xChartDoc);
860 
861     pFS->endElement( FSNS( XML_c, XML_chartSpace ) );
862 }
863 
exportExternalData(const Reference<css::chart::XChartDocument> & xChartDoc)864 void ChartExport::exportExternalData( const Reference< css::chart::XChartDocument >& xChartDoc )
865 {
866     // Embedded external data is grab bagged for docx file hence adding export part of
867     // external data for docx files only.
868     if(GetDocumentType() != DOCUMENT_DOCX)
869         return;
870 
871     OUString externalDataPath;
872     Reference< beans::XPropertySet > xDocPropSet( xChartDoc->getDiagram(), uno::UNO_QUERY );
873     if( xDocPropSet.is())
874     {
875         try
876         {
877             Any aAny( xDocPropSet->getPropertyValue( "ExternalData" ));
878             aAny >>= externalDataPath;
879         }
880         catch( beans::UnknownPropertyException & )
881         {
882             SAL_WARN("oox", "Required property not found in ChartDocument");
883         }
884     }
885     if(!externalDataPath.isEmpty())
886     {
887         // Here adding external data entry to relationship.
888         OUString relationPath = externalDataPath;
889         // Converting absolute path to relative path.
890         if( externalDataPath[ 0 ] != '.' && externalDataPath[ 1 ] != '.')
891         {
892             sal_Int32 nSepPos = externalDataPath.indexOf( '/', 0 );
893             if( nSepPos > 0)
894             {
895                 relationPath = relationPath.copy( nSepPos,  ::std::max< sal_Int32 >( externalDataPath.getLength(), 0 ) -  nSepPos );
896                 relationPath = ".." + relationPath;
897             }
898         }
899         FSHelperPtr pFS = GetFS();
900         OUString type = oox::getRelationship(Relationship::PACKAGE);
901         if (relationPath.endsWith(".bin"))
902             type = oox::getRelationship(Relationship::OLEOBJECT);
903 
904         OUString sRelId = GetFB()->addRelation(pFS->getOutputStream(),
905                         type,
906                         relationPath);
907         pFS->singleElementNS(XML_c, XML_externalData, FSNS(XML_r, XML_id), sRelId.toUtf8());
908     }
909 }
910 
exportChart(const Reference<css::chart::XChartDocument> & xChartDoc)911 void ChartExport::exportChart( const Reference< css::chart::XChartDocument >& xChartDoc )
912 {
913     Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
914     mxDiagram.set( xChartDoc->getDiagram() );
915     if( xNewDoc.is())
916         mxNewDiagram.set( xNewDoc->getFirstDiagram());
917 
918     // get Properties of ChartDocument
919     bool bHasMainTitle = false;
920     OUString aSubTitle;
921     bool bHasLegend = false;
922     Reference< beans::XPropertySet > xDocPropSet( xChartDoc, uno::UNO_QUERY );
923     if( xDocPropSet.is())
924     {
925         try
926         {
927             Any aAny( xDocPropSet->getPropertyValue("HasMainTitle"));
928             aAny >>= bHasMainTitle;
929             aAny = xDocPropSet->getPropertyValue("HasLegend");
930             aAny >>= bHasLegend;
931         }
932         catch( beans::UnknownPropertyException & )
933         {
934             SAL_WARN("oox", "Required property not found in ChartDocument");
935         }
936     } // if( xDocPropSet.is())
937 
938     Reference< beans::XPropertySet > xPropSubTitle( xChartDoc->getSubTitle(), UNO_QUERY );
939     if( xPropSubTitle.is())
940     {
941         try
942         {
943             xPropSubTitle->getPropertyValue("String") >>= aSubTitle;
944         }
945         catch( beans::UnknownPropertyException & )
946         {
947         }
948     }
949 
950     // chart element
951     FSHelperPtr pFS = GetFS();
952     pFS->startElement(FSNS(XML_c, XML_chart));
953 
954     // titles
955     if( bHasMainTitle )
956     {
957         exportTitle( xChartDoc->getTitle(), !aSubTitle.isEmpty() ? &aSubTitle : nullptr );
958         pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
959     }
960     else if( !aSubTitle.isEmpty() )
961     {
962         exportTitle( xChartDoc->getSubTitle(), nullptr );
963         pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
964     }
965     else
966     {
967         pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "1");
968     }
969 
970     InitPlotArea( );
971     if( mbIs3DChart )
972     {
973         exportView3D();
974 
975         // floor
976         Reference< beans::XPropertySet > xFloor = mxNewDiagram->getFloor();
977         if( xFloor.is() )
978         {
979             pFS->startElement(FSNS(XML_c, XML_floor));
980             exportShapeProps( xFloor );
981             pFS->endElement( FSNS( XML_c, XML_floor ) );
982         }
983 
984         // LibreOffice doesn't distinguish between sideWall and backWall (both are using the same color).
985         // It is controlled by the same Wall property.
986         Reference< beans::XPropertySet > xWall = mxNewDiagram->getWall();
987         if( xWall.is() )
988         {
989             // sideWall
990             pFS->startElement(FSNS(XML_c, XML_sideWall));
991             exportShapeProps( xWall );
992             pFS->endElement( FSNS( XML_c, XML_sideWall ) );
993 
994             // backWall
995             pFS->startElement(FSNS(XML_c, XML_backWall));
996             exportShapeProps( xWall );
997             pFS->endElement( FSNS( XML_c, XML_backWall ) );
998         }
999 
1000     }
1001     // plot area
1002     exportPlotArea( xChartDoc );
1003     // legend
1004     if( bHasLegend )
1005         exportLegend( xChartDoc );
1006 
1007     uno::Reference<beans::XPropertySet> xDiagramPropSet(xChartDoc->getDiagram(), uno::UNO_QUERY);
1008     uno::Any aPlotVisOnly = xDiagramPropSet->getPropertyValue("IncludeHiddenCells");
1009     bool bIncludeHiddenCells = false;
1010     aPlotVisOnly >>= bIncludeHiddenCells;
1011     pFS->singleElement(FSNS(XML_c, XML_plotVisOnly), XML_val, ToPsz10(!bIncludeHiddenCells));
1012 
1013     exportMissingValueTreatment(Reference<beans::XPropertySet>(mxDiagram, uno::UNO_QUERY));
1014 
1015     pFS->endElement( FSNS( XML_c, XML_chart ) );
1016 }
1017 
exportMissingValueTreatment(const uno::Reference<beans::XPropertySet> & xPropSet)1018 void ChartExport::exportMissingValueTreatment(const uno::Reference<beans::XPropertySet>& xPropSet)
1019 {
1020     if (!xPropSet.is())
1021         return;
1022 
1023     sal_Int32 nVal = 0;
1024     uno::Any aAny = xPropSet->getPropertyValue("MissingValueTreatment");
1025     if (!(aAny >>= nVal))
1026         return;
1027 
1028     const char* pVal = nullptr;
1029     switch (nVal)
1030     {
1031         case cssc::MissingValueTreatment::LEAVE_GAP:
1032             pVal = "gap";
1033         break;
1034         case cssc::MissingValueTreatment::USE_ZERO:
1035             pVal = "zero";
1036         break;
1037         case cssc::MissingValueTreatment::CONTINUE:
1038             pVal = "span";
1039         break;
1040         default:
1041             SAL_WARN("oox", "unknown MissingValueTreatment value");
1042         break;
1043     }
1044 
1045     FSHelperPtr pFS = GetFS();
1046     pFS->singleElement(FSNS(XML_c, XML_dispBlanksAs), XML_val, pVal);
1047 }
1048 
exportLegend(const Reference<css::chart::XChartDocument> & xChartDoc)1049 void ChartExport::exportLegend( const Reference< css::chart::XChartDocument >& xChartDoc )
1050 {
1051     FSHelperPtr pFS = GetFS();
1052     pFS->startElement(FSNS(XML_c, XML_legend));
1053 
1054     Reference< beans::XPropertySet > xProp( xChartDoc->getLegend(), uno::UNO_QUERY );
1055     if( xProp.is() )
1056     {
1057         // position
1058         css::chart::ChartLegendPosition aLegendPos = css::chart::ChartLegendPosition_NONE;
1059         try
1060         {
1061             Any aAny( xProp->getPropertyValue( "Alignment" ));
1062             aAny >>= aLegendPos;
1063         }
1064         catch( beans::UnknownPropertyException & )
1065         {
1066             SAL_WARN("oox", "Property Align not found in ChartLegend");
1067         }
1068 
1069         const char* strPos = nullptr;
1070         switch( aLegendPos )
1071         {
1072             case css::chart::ChartLegendPosition_LEFT:
1073                 strPos = "l";
1074                 break;
1075             case css::chart::ChartLegendPosition_RIGHT:
1076                 strPos = "r";
1077                 break;
1078             case css::chart::ChartLegendPosition_TOP:
1079                 strPos = "t";
1080                 break;
1081             case css::chart::ChartLegendPosition_BOTTOM:
1082                 strPos = "b";
1083                 break;
1084             case css::chart::ChartLegendPosition_NONE:
1085             case css::chart::ChartLegendPosition::ChartLegendPosition_MAKE_FIXED_SIZE:
1086                 // nothing
1087                 break;
1088         }
1089 
1090         if( strPos != nullptr )
1091         {
1092             pFS->singleElement(FSNS(XML_c, XML_legendPos), XML_val, strPos);
1093         }
1094 
1095         uno::Any aRelativePos = xProp->getPropertyValue("RelativePosition");
1096         if (aRelativePos.hasValue())
1097         {
1098             pFS->startElement(FSNS(XML_c, XML_layout));
1099             pFS->startElement(FSNS(XML_c, XML_manualLayout));
1100 
1101             pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
1102             pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
1103             chart2::RelativePosition aPos = aRelativePos.get<chart2::RelativePosition>();
1104 
1105             const double x = aPos.Primary;
1106             const double y = aPos.Secondary;
1107 
1108             pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
1109             pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
1110 
1111             uno::Any aRelativeSize = xProp->getPropertyValue("RelativeSize");
1112             if (aRelativeSize.hasValue())
1113             {
1114                 chart2::RelativeSize aSize = aRelativeSize.get<chart2::RelativeSize>();
1115 
1116                 const double w = aSize.Primary;
1117                 const double h = aSize.Secondary;
1118 
1119                 pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
1120 
1121                 pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
1122             }
1123 
1124             SAL_WARN_IF(aPos.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position");
1125 
1126             pFS->endElement(FSNS(XML_c, XML_manualLayout));
1127             pFS->endElement(FSNS(XML_c, XML_layout));
1128         }
1129 
1130         if (strPos != nullptr)
1131         {
1132             pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0");
1133         }
1134 
1135         // shape properties
1136         exportShapeProps( xProp );
1137 
1138         // draw-chart:txPr text properties
1139         exportTextProps( xProp );
1140     }
1141 
1142     // legendEntry
1143 
1144     pFS->endElement( FSNS( XML_c, XML_legend ) );
1145 }
1146 
exportTitle(const Reference<XShape> & xShape,const OUString * pSubText)1147 void ChartExport::exportTitle( const Reference< XShape >& xShape, const OUString* pSubText)
1148 {
1149     OUString sText;
1150     Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
1151     if( xPropSet.is())
1152     {
1153         xPropSet->getPropertyValue("String") >>= sText;
1154     }
1155 
1156     // tdf#101322: add subtitle to title
1157     if( pSubText )
1158         sText = sText.isEmpty() ? *pSubText : sText + "\n" + *pSubText;
1159 
1160     if( sText.isEmpty() )
1161         return;
1162 
1163     FSHelperPtr pFS = GetFS();
1164     pFS->startElement(FSNS(XML_c, XML_title));
1165 
1166     pFS->startElement(FSNS(XML_c, XML_tx));
1167     pFS->startElement(FSNS(XML_c, XML_rich));
1168 
1169     // TODO: bodyPr
1170     const char* sWritingMode = nullptr;
1171     bool bVertical = false;
1172     xPropSet->getPropertyValue("StackedText") >>= bVertical;
1173     if( bVertical )
1174         sWritingMode = "wordArtVert";
1175 
1176     sal_Int32 nRotation = 0;
1177     xPropSet->getPropertyValue("TextRotation") >>= nRotation;
1178 
1179     pFS->singleElement( FSNS( XML_a, XML_bodyPr ),
1180             XML_vert, sWritingMode,
1181             XML_rot, oox::drawingml::calcRotationValue(nRotation) );
1182     // TODO: lstStyle
1183     pFS->singleElement(FSNS(XML_a, XML_lstStyle));
1184     // FIXME: handle multiple paragraphs to parse aText
1185     pFS->startElement(FSNS(XML_a, XML_p));
1186 
1187     pFS->startElement(FSNS(XML_a, XML_pPr));
1188 
1189     bool bDummy = false;
1190     sal_Int32 nDummy;
1191     WriteRunProperties(xPropSet, false, XML_defRPr, true, bDummy, nDummy );
1192 
1193     pFS->endElement( FSNS( XML_a, XML_pPr ) );
1194 
1195     pFS->startElement(FSNS(XML_a, XML_r));
1196     bDummy = false;
1197     WriteRunProperties( xPropSet, false, XML_rPr, true, bDummy, nDummy );
1198     pFS->startElement(FSNS(XML_a, XML_t));
1199     pFS->writeEscaped( sText );
1200     pFS->endElement( FSNS( XML_a, XML_t ) );
1201     pFS->endElement( FSNS( XML_a, XML_r ) );
1202 
1203     pFS->endElement( FSNS( XML_a, XML_p ) );
1204 
1205     pFS->endElement( FSNS( XML_c, XML_rich ) );
1206     pFS->endElement( FSNS( XML_c, XML_tx ) );
1207 
1208     uno::Any aManualLayout = xPropSet->getPropertyValue("RelativePosition");
1209     if (aManualLayout.hasValue())
1210     {
1211         pFS->startElement(FSNS(XML_c, XML_layout));
1212         pFS->startElement(FSNS(XML_c, XML_manualLayout));
1213         pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
1214         pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
1215 
1216         Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
1217         awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
1218 
1219         awt::Size aSize = xShape->getSize();
1220         awt::Point aPos2 = xShape->getPosition();
1221         // rotated shapes need special handling...
1222         double fSin = fabs(sin(basegfx::deg2rad(nRotation*0.01)));
1223         // remove part of height from X direction, if title is rotated down
1224         if( nRotation*0.01 > 180.0 )
1225             aPos2.X -= static_cast<sal_Int32>(fSin * aSize.Height + 0.5);
1226         // remove part of width from Y direction, if title is rotated up
1227         else if( nRotation*0.01 > 0.0 )
1228             aPos2.Y -= static_cast<sal_Int32>(fSin * aSize.Width + 0.5);
1229 
1230         double x = static_cast<double>(aPos2.X) / static_cast<double>(aPageSize.Width);
1231         double y = static_cast<double>(aPos2.Y) / static_cast<double>(aPageSize.Height);
1232         /*
1233         pFS->singleElement(FSNS(XML_c, XML_wMode), XML_val, "edge");
1234         pFS->singleElement(FSNS(XML_c, XML_hMode), XML_val, "edge");
1235                 */
1236         pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
1237         pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
1238         /*
1239         pFS->singleElement(FSNS(XML_c, XML_w), XML_val, "");
1240         pFS->singleElement(FSNS(XML_c, XML_h), XML_val, "");
1241                 */
1242         pFS->endElement(FSNS(XML_c, XML_manualLayout));
1243         pFS->endElement(FSNS(XML_c, XML_layout));
1244     }
1245 
1246     pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0");
1247 
1248     // shape properties
1249     if( xPropSet.is() )
1250     {
1251         exportShapeProps( xPropSet );
1252     }
1253 
1254     pFS->endElement( FSNS( XML_c, XML_title ) );
1255 }
1256 
exportPlotArea(const Reference<css::chart::XChartDocument> & xChartDoc)1257 void ChartExport::exportPlotArea( const Reference< css::chart::XChartDocument >& xChartDoc )
1258 {
1259     Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, uno::UNO_QUERY );
1260     if( ! xBCooSysCnt.is())
1261         return;
1262 
1263     // plot-area element
1264 
1265     FSHelperPtr pFS = GetFS();
1266     pFS->startElement(FSNS(XML_c, XML_plotArea));
1267 
1268     Reference<beans::XPropertySet> xWall(mxNewDiagram, uno::UNO_QUERY);
1269     if( xWall.is() )
1270     {
1271         uno::Any aAny = xWall->getPropertyValue("RelativePosition");
1272         if (aAny.hasValue())
1273         {
1274             chart2::RelativePosition aPos = aAny.get<chart2::RelativePosition>();
1275             aAny = xWall->getPropertyValue("RelativeSize");
1276             chart2::RelativeSize aSize = aAny.get<chart2::RelativeSize>();
1277             uno::Reference< css::chart::XDiagramPositioning > xDiagramPositioning( xChartDoc->getDiagram(), uno::UNO_QUERY );
1278             exportManualLayout(aPos, aSize, xDiagramPositioning->isExcludingDiagramPositioning() );
1279         }
1280     }
1281 
1282     // chart type
1283     const Sequence< Reference< chart2::XCoordinateSystem > >
1284         aCooSysSeq( xBCooSysCnt->getCoordinateSystems());
1285     for( const auto& rCS : aCooSysSeq )
1286     {
1287         Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY );
1288         if( ! xCTCnt.is())
1289             continue;
1290         mnSeriesCount=0;
1291         const Sequence< Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes());
1292         for( const auto& rCT : aCTSeq )
1293         {
1294             Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, uno::UNO_QUERY );
1295             if( ! xDSCnt.is())
1296                 return;
1297             Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY );
1298             if( ! xChartType.is())
1299                 continue;
1300             // note: if xDSCnt.is() then also aCTSeq[nCTIdx]
1301             OUString aChartType( xChartType->getChartType());
1302             sal_Int32 eChartType = lcl_getChartType( aChartType );
1303             switch( eChartType )
1304             {
1305                 case chart::TYPEID_BAR:
1306                     {
1307                         exportBarChart( xChartType );
1308                         break;
1309                     }
1310                 case chart::TYPEID_AREA:
1311                     {
1312                         exportAreaChart( xChartType );
1313                         break;
1314                     }
1315                 case chart::TYPEID_LINE:
1316                     {
1317                         exportLineChart( xChartType );
1318                         break;
1319                     }
1320                 case chart::TYPEID_BUBBLE:
1321                     {
1322                         exportBubbleChart( xChartType );
1323                         break;
1324                     }
1325                 case chart::TYPEID_OFPIE:
1326                     {
1327                         break;
1328                     }
1329                 case chart::TYPEID_DOUGHNUT:
1330                 case chart::TYPEID_PIE:
1331                     {
1332                         exportPieChart( xChartType );
1333                         break;
1334                     }
1335                 case chart::TYPEID_RADARLINE:
1336                 case chart::TYPEID_RADARAREA:
1337                     {
1338                         exportRadarChart( xChartType );
1339                         break;
1340                     }
1341                 case chart::TYPEID_SCATTER:
1342                     {
1343                         exportScatterChart( xChartType );
1344                         break;
1345                     }
1346                 case chart::TYPEID_STOCK:
1347                     {
1348                         exportStockChart( xChartType );
1349                         break;
1350                     }
1351                 case chart::TYPEID_SURFACE:
1352                     {
1353                         exportSurfaceChart( xChartType );
1354                         break;
1355                     }
1356                 default:
1357                     {
1358                         SAL_WARN("oox", "ChartExport::exportPlotArea -- not support chart type");
1359                         break;
1360                     }
1361             }
1362 
1363         }
1364     }
1365     //Axis Data
1366     exportAxes( );
1367     // Data Table
1368     exportDataTable();
1369 
1370     // shape properties
1371     /*
1372      * Export the Plot area Shape Properties
1373      * eg: Fill and Outline
1374      */
1375     Reference< css::chart::X3DDisplay > xWallFloorSupplier( mxDiagram, uno::UNO_QUERY );
1376     // tdf#114139 For 2D charts Plot Area equivalent is Chart Wall.
1377     // Unfortunately LibreOffice doesn't have Plot Area equivalent for 3D charts.
1378     // It means that Plot Area couldn't be displayed and changed for 3D chars in LibreOffice.
1379     // We cannot write Wall attributes into Plot Area for 3D charts, because Wall us used as background wall.
1380     if( !mbIs3DChart && xWallFloorSupplier.is() )
1381     {
1382         Reference< beans::XPropertySet > xWallPropSet = xWallFloorSupplier->getWall();
1383         if( xWallPropSet.is() )
1384         {
1385             uno::Any aAny = xWallPropSet->getPropertyValue("LineStyle");
1386             sal_Int32 eChartType = getChartType( );
1387             // Export LineStyle_NONE instead of default linestyle of PlotArea border, because LibreOffice
1388             // make invisible the Wall shape properties, in case of these charts. Or in the future set
1389             // the default LineStyle of these charts to LineStyle_NONE.
1390             bool noSupportWallProp = ( (eChartType == chart::TYPEID_PIE) || (eChartType == chart::TYPEID_RADARLINE) || (eChartType == chart::TYPEID_RADARAREA) );
1391             if ( noSupportWallProp && (aAny != drawing::LineStyle_NONE) )
1392             {
1393                 xWallPropSet->setPropertyValue( "LineStyle", uno::Any(drawing::LineStyle_NONE) );
1394             }
1395             exportShapeProps( xWallPropSet );
1396         }
1397     }
1398 
1399     pFS->endElement( FSNS( XML_c, XML_plotArea ) );
1400 
1401 }
1402 
exportManualLayout(const css::chart2::RelativePosition & rPos,const css::chart2::RelativeSize & rSize,const bool bIsExcludingDiagramPositioning)1403 void ChartExport::exportManualLayout(const css::chart2::RelativePosition& rPos,
1404                                      const css::chart2::RelativeSize& rSize,
1405                                      const bool bIsExcludingDiagramPositioning)
1406 {
1407     FSHelperPtr pFS = GetFS();
1408     pFS->startElement(FSNS(XML_c, XML_layout));
1409     pFS->startElement(FSNS(XML_c, XML_manualLayout));
1410 
1411     // By default layoutTarget is set to "outer" and we shouldn't save it in that case
1412     if ( bIsExcludingDiagramPositioning )
1413     {
1414         pFS->singleElement(FSNS(XML_c, XML_layoutTarget), XML_val, "inner");
1415     }
1416     pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
1417     pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
1418 
1419     double x = rPos.Primary;
1420     double y = rPos.Secondary;
1421     const double w = rSize.Primary;
1422     const double h = rSize.Secondary;
1423     switch (rPos.Anchor)
1424     {
1425         case drawing::Alignment_LEFT:
1426             y -= (h/2);
1427         break;
1428         case drawing::Alignment_TOP_LEFT:
1429         break;
1430         case drawing::Alignment_BOTTOM_LEFT:
1431             y -= h;
1432         break;
1433         case drawing::Alignment_TOP:
1434             x -= (w/2);
1435         break;
1436         case drawing::Alignment_CENTER:
1437             x -= (w/2);
1438             y -= (h/2);
1439         break;
1440         case drawing::Alignment_BOTTOM:
1441             x -= (w/2);
1442             y -= h;
1443         break;
1444         case drawing::Alignment_TOP_RIGHT:
1445             x -= w;
1446         break;
1447         case drawing::Alignment_BOTTOM_RIGHT:
1448             x -= w;
1449             y -= h;
1450         break;
1451         case drawing::Alignment_RIGHT:
1452             y -= (h/2);
1453             x -= w;
1454         break;
1455         default:
1456             SAL_WARN("oox", "unhandled alignment case for manual layout export " << static_cast<sal_uInt16>(rPos.Anchor));
1457     }
1458 
1459     pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
1460 
1461     pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
1462 
1463     pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
1464 
1465     pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
1466 
1467     pFS->endElement(FSNS(XML_c, XML_manualLayout));
1468     pFS->endElement(FSNS(XML_c, XML_layout));
1469 }
1470 
exportFill(const Reference<XPropertySet> & xPropSet)1471 void ChartExport::exportFill( const Reference< XPropertySet >& xPropSet )
1472 {
1473     if ( !GetProperty( xPropSet, "FillStyle" ) )
1474         return;
1475     FillStyle aFillStyle( FillStyle_NONE );
1476     xPropSet->getPropertyValue( "FillStyle" ) >>= aFillStyle;
1477     switch( aFillStyle )
1478     {
1479         case FillStyle_GRADIENT :
1480             exportGradientFill( xPropSet );
1481             break;
1482         case FillStyle_BITMAP :
1483             exportBitmapFill( xPropSet );
1484             break;
1485         case FillStyle_HATCH:
1486             exportHatch(xPropSet);
1487         break;
1488         default:
1489             WriteFill( xPropSet );
1490     }
1491 }
1492 
exportHatch(const Reference<XPropertySet> & xPropSet)1493 void ChartExport::exportHatch( const Reference< XPropertySet >& xPropSet )
1494 {
1495     if (!xPropSet.is())
1496         return;
1497 
1498     if (GetProperty(xPropSet, "FillHatchName"))
1499     {
1500         OUString aHatchName;
1501         mAny >>= aHatchName;
1502         uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
1503         uno::Reference< container::XNameAccess > xHatchTable( xFact->createInstance("com.sun.star.drawing.HatchTable"), uno::UNO_QUERY );
1504         uno::Any rValue = xHatchTable->getByName(aHatchName);
1505         css::drawing::Hatch aHatch;
1506         rValue >>= aHatch;
1507         WritePattFill(xPropSet, aHatch);
1508     }
1509 
1510 }
1511 
exportBitmapFill(const Reference<XPropertySet> & xPropSet)1512 void ChartExport::exportBitmapFill( const Reference< XPropertySet >& xPropSet )
1513 {
1514     if( xPropSet.is() )
1515      {
1516         OUString sFillBitmapName;
1517         xPropSet->getPropertyValue("FillBitmapName") >>= sFillBitmapName;
1518 
1519         uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
1520         try
1521         {
1522             uno::Reference< container::XNameAccess > xBitmapTable( xFact->createInstance("com.sun.star.drawing.BitmapTable"), uno::UNO_QUERY );
1523             uno::Any rValue = xBitmapTable->getByName( sFillBitmapName );
1524             if (rValue.has<uno::Reference<awt::XBitmap>>())
1525             {
1526                 uno::Reference<awt::XBitmap> xBitmap = rValue.get<uno::Reference<awt::XBitmap>>();
1527                 uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY);
1528                 if (xGraphic.is())
1529                 {
1530                     WriteXGraphicBlipFill(xPropSet, xGraphic, XML_a, true, true);
1531                 }
1532             }
1533         }
1534         catch (const uno::Exception &)
1535         {
1536             TOOLS_WARN_EXCEPTION("oox", "ChartExport::exportBitmapFill");
1537         }
1538     }
1539 }
1540 
exportGradientFill(const Reference<XPropertySet> & xPropSet)1541 void ChartExport::exportGradientFill( const Reference< XPropertySet >& xPropSet )
1542 {
1543     if( xPropSet.is() )
1544      {
1545         OUString sFillGradientName;
1546         xPropSet->getPropertyValue("FillGradientName") >>= sFillGradientName;
1547 
1548         awt::Gradient aGradient;
1549         awt::Gradient aTransparenceGradient;
1550         uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
1551         try
1552         {
1553             uno::Reference< container::XNameAccess > xGradient( xFact->createInstance("com.sun.star.drawing.GradientTable"), uno::UNO_QUERY );
1554             uno::Any rGradientValue = xGradient->getByName( sFillGradientName );
1555             if( rGradientValue >>= aGradient )
1556             {
1557                 mpFS->startElementNS(XML_a, XML_gradFill);
1558                 OUString sFillTransparenceGradientName;
1559                 if( (xPropSet->getPropertyValue("FillTransparenceGradientName") >>= sFillTransparenceGradientName) && !sFillTransparenceGradientName.isEmpty())
1560                 {
1561                     uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
1562                     uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName);
1563                     rTransparenceValue >>= aTransparenceGradient;;
1564                     WriteGradientFill(aGradient, aTransparenceGradient);
1565                 }
1566                 else
1567                 {
1568                     WriteGradientFill(aGradient, aTransparenceGradient, xPropSet);
1569                 }
1570                 mpFS->endElementNS(XML_a, XML_gradFill);
1571             }
1572         }
1573         catch (const uno::Exception &)
1574         {
1575             TOOLS_INFO_EXCEPTION("oox", "ChartExport::exportGradientFill");
1576         }
1577 
1578     }
1579 }
1580 
exportDataTable()1581 void ChartExport::exportDataTable( )
1582 {
1583     FSHelperPtr pFS = GetFS();
1584     Reference< beans::XPropertySet > aPropSet( mxDiagram, uno::UNO_QUERY );
1585 
1586     bool bShowVBorder = false;
1587     bool bShowHBorder = false;
1588     bool bShowOutline = false;
1589 
1590     if (GetProperty( aPropSet, "DataTableHBorder"))
1591         mAny >>= bShowHBorder;
1592     if (GetProperty( aPropSet, "DataTableVBorder"))
1593         mAny >>= bShowVBorder;
1594     if (GetProperty( aPropSet, "DataTableOutline"))
1595         mAny >>= bShowOutline;
1596 
1597     if (bShowVBorder || bShowHBorder || bShowOutline)
1598     {
1599         pFS->startElement(FSNS(XML_c, XML_dTable));
1600         if (bShowHBorder)
1601             pFS->singleElement( FSNS( XML_c, XML_showHorzBorder ),
1602                             XML_val, "1" );
1603         if (bShowVBorder)
1604             pFS->singleElement(FSNS(XML_c, XML_showVertBorder), XML_val, "1");
1605         if (bShowOutline)
1606             pFS->singleElement(FSNS(XML_c, XML_showOutline), XML_val, "1");
1607 
1608         pFS->endElement(  FSNS( XML_c, XML_dTable));
1609     }
1610 
1611 }
exportAreaChart(const Reference<chart2::XChartType> & xChartType)1612 void ChartExport::exportAreaChart( const Reference< chart2::XChartType >& xChartType )
1613 {
1614     FSHelperPtr pFS = GetFS();
1615     sal_Int32 nTypeId = XML_areaChart;
1616     if( mbIs3DChart )
1617         nTypeId = XML_area3DChart;
1618     pFS->startElement(FSNS(XML_c, nTypeId));
1619 
1620     exportGrouping( );
1621     bool bPrimaryAxes = true;
1622     exportAllSeries(xChartType, bPrimaryAxes);
1623     exportAxesId(bPrimaryAxes);
1624 
1625     pFS->endElement( FSNS( XML_c, nTypeId ) );
1626 }
1627 
exportBarChart(const Reference<chart2::XChartType> & xChartType)1628 void ChartExport::exportBarChart( const Reference< chart2::XChartType >& xChartType )
1629 {
1630     sal_Int32 nTypeId = XML_barChart;
1631     if( mbIs3DChart )
1632         nTypeId = XML_bar3DChart;
1633     FSHelperPtr pFS = GetFS();
1634     pFS->startElement(FSNS(XML_c, nTypeId));
1635     // bar direction
1636     bool bVertical = false;
1637     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
1638     if( GetProperty( xPropSet, "Vertical" ) )
1639         mAny >>= bVertical;
1640 
1641     const char* bardir = bVertical? "bar":"col";
1642     pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, bardir);
1643 
1644     exportGrouping( true );
1645 
1646     exportVaryColors(xChartType);
1647 
1648     bool bPrimaryAxes = true;
1649     exportAllSeries(xChartType, bPrimaryAxes);
1650 
1651     Reference< XPropertySet > xTypeProp( xChartType, uno::UNO_QUERY );
1652 
1653     if( xTypeProp.is() && GetProperty( xTypeProp, "GapwidthSequence") )
1654     {
1655         uno::Sequence< sal_Int32 > aBarPositionSequence;
1656         mAny >>= aBarPositionSequence;
1657         if( aBarPositionSequence.hasElements() )
1658         {
1659             sal_Int32 nGapWidth = aBarPositionSequence[0];
1660             pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(nGapWidth));
1661         }
1662     }
1663 
1664     if( mbIs3DChart )
1665     {
1666         // Shape
1667         namespace cssc = css::chart;
1668         sal_Int32 nGeom3d = cssc::ChartSolidType::RECTANGULAR_SOLID;
1669         if( xPropSet.is() && GetProperty( xPropSet, "SolidType") )
1670             mAny >>= nGeom3d;
1671         const char* sShapeType = nullptr;
1672         switch( nGeom3d )
1673         {
1674             case cssc::ChartSolidType::RECTANGULAR_SOLID:
1675                 sShapeType = "box";
1676                 break;
1677             case cssc::ChartSolidType::CONE:
1678                 sShapeType = "cone";
1679                 break;
1680             case cssc::ChartSolidType::CYLINDER:
1681                 sShapeType = "cylinder";
1682                 break;
1683             case cssc::ChartSolidType::PYRAMID:
1684                 sShapeType = "pyramid";
1685                 break;
1686         }
1687         pFS->singleElement(FSNS(XML_c, XML_shape), XML_val, sShapeType);
1688     }
1689 
1690     //overlap
1691     if( !mbIs3DChart && xTypeProp.is() && GetProperty( xTypeProp, "OverlapSequence") )
1692     {
1693         uno::Sequence< sal_Int32 > aBarPositionSequence;
1694         mAny >>= aBarPositionSequence;
1695         if( aBarPositionSequence.hasElements() )
1696         {
1697             sal_Int32 nOverlap = aBarPositionSequence[0];
1698             // Stacked/Percent Bar/Column chart Overlap-workaround
1699             // Export the Overlap value with 100% for stacked charts,
1700             // because the default overlap value of the Bar/Column chart is 0% and
1701             // LibreOffice do nothing with the overlap value in Stacked charts case,
1702             // unlike the MS Office, which is interpreted differently.
1703             if( ( mbStacked || mbPercent ) && nOverlap != 100 )
1704             {
1705                 nOverlap = 100;
1706                 pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
1707             }
1708             else // Normal bar chart
1709             {
1710                 pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
1711             }
1712         }
1713     }
1714 
1715     exportAxesId(bPrimaryAxes);
1716 
1717     pFS->endElement( FSNS( XML_c, nTypeId ) );
1718 }
1719 
exportBubbleChart(const Reference<chart2::XChartType> & xChartType)1720 void ChartExport::exportBubbleChart( const Reference< chart2::XChartType >& xChartType )
1721 {
1722     FSHelperPtr pFS = GetFS();
1723     pFS->startElement(FSNS(XML_c, XML_bubbleChart));
1724 
1725     exportVaryColors(xChartType);
1726 
1727     bool bPrimaryAxes = true;
1728     exportAllSeries(xChartType, bPrimaryAxes);
1729 
1730     exportAxesId(bPrimaryAxes);
1731 
1732     pFS->endElement( FSNS( XML_c, XML_bubbleChart ) );
1733 }
1734 
exportDoughnutChart(const Reference<chart2::XChartType> & xChartType)1735 void ChartExport::exportDoughnutChart( const Reference< chart2::XChartType >& xChartType )
1736 {
1737     FSHelperPtr pFS = GetFS();
1738     pFS->startElement(FSNS(XML_c, XML_doughnutChart));
1739 
1740     exportVaryColors(xChartType);
1741 
1742     bool bPrimaryAxes = true;
1743     exportAllSeries(xChartType, bPrimaryAxes);
1744     // firstSliceAng
1745     exportFirstSliceAng( );
1746     //FIXME: holeSize
1747     pFS->singleElement(FSNS(XML_c, XML_holeSize), XML_val, OString::number(50));
1748 
1749     pFS->endElement( FSNS( XML_c, XML_doughnutChart ) );
1750 }
1751 
1752 namespace {
1753 
splitDataSeriesByAxis(const Reference<chart2::XChartType> & xChartType)1754 std::vector<Sequence<Reference<chart2::XDataSeries> > > splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType)
1755 {
1756     std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries;
1757     std::map<sal_Int32, size_t> aMapAxisToIndex;
1758 
1759     Reference< chart2::XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY );
1760     if(xDSCnt.is())
1761     {
1762         sal_Int32 nAxisIndexOfFirstSeries = -1;
1763         const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries());
1764         for (const uno::Reference<chart2::XDataSeries>& xSeries : aSeriesSeq)
1765         {
1766             Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
1767             if (!xPropSet.is())
1768                 continue;
1769 
1770             sal_Int32 nAxisIndex = -1;
1771             uno::Any aAny = xPropSet->getPropertyValue("AttachedAxisIndex");
1772             aAny >>= nAxisIndex;
1773             size_t nVectorPos = 0;
1774             if (nAxisIndexOfFirstSeries == -1)
1775             {
1776                 nAxisIndexOfFirstSeries = nAxisIndex;
1777             }
1778 
1779             auto it = aMapAxisToIndex.find(nAxisIndex);
1780             if (it == aMapAxisToIndex.end())
1781             {
1782                 aSplitSeries.emplace_back();
1783                 nVectorPos = aSplitSeries.size() - 1;
1784                 aMapAxisToIndex.insert(std::pair<sal_Int32, size_t>(nAxisIndex, nVectorPos));
1785             }
1786             else
1787             {
1788                 nVectorPos = it->second;
1789             }
1790 
1791             uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq = aSplitSeries[nVectorPos];
1792             sal_Int32 nLength = rAxisSeriesSeq.getLength();
1793             rAxisSeriesSeq.realloc(nLength + 1);
1794             rAxisSeriesSeq[nLength] = xSeries;
1795         }
1796         // if the first series attached to secondary axis, then export those series first, which are attached to primary axis
1797         // also the MS Office export every time in this order
1798         if ( aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1 )
1799         {
1800             std::swap( aSplitSeries[0], aSplitSeries[1] );
1801         }
1802     }
1803 
1804     return aSplitSeries;
1805 }
1806 
1807 }
1808 
exportLineChart(const Reference<chart2::XChartType> & xChartType)1809 void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChartType )
1810 {
1811     FSHelperPtr pFS = GetFS();
1812     std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
1813     for (auto & splitDataSeries : aSplitDataSeries)
1814     {
1815         if (!splitDataSeries.hasElements())
1816             continue;
1817 
1818         sal_Int32 nTypeId = XML_lineChart;
1819         if( mbIs3DChart )
1820             nTypeId = XML_line3DChart;
1821         pFS->startElement(FSNS(XML_c, nTypeId));
1822 
1823         exportGrouping( );
1824 
1825         exportVaryColors(xChartType);
1826         // TODO: show marker symbol in series?
1827         bool bPrimaryAxes = true;
1828         exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
1829 
1830         // show marker?
1831         sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
1832         Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
1833         if( GetProperty( xPropSet, "SymbolType" ) )
1834             mAny >>= nSymbolType;
1835 
1836         if( !mbIs3DChart )
1837         {
1838             exportHiLowLines();
1839             exportUpDownBars(xChartType);
1840             const char* marker = nSymbolType == css::chart::ChartSymbolType::NONE? "0":"1";
1841             pFS->singleElement(FSNS(XML_c, XML_marker), XML_val, marker);
1842         }
1843 
1844         exportAxesId(bPrimaryAxes, true);
1845 
1846         pFS->endElement( FSNS( XML_c, nTypeId ) );
1847     }
1848 }
1849 
exportPieChart(const Reference<chart2::XChartType> & xChartType)1850 void ChartExport::exportPieChart( const Reference< chart2::XChartType >& xChartType )
1851 {
1852     sal_Int32 eChartType = getChartType( );
1853     if(eChartType == chart::TYPEID_DOUGHNUT)
1854     {
1855         exportDoughnutChart( xChartType );
1856         return;
1857     }
1858     FSHelperPtr pFS = GetFS();
1859     sal_Int32 nTypeId = XML_pieChart;
1860     if( mbIs3DChart )
1861         nTypeId = XML_pie3DChart;
1862     pFS->startElement(FSNS(XML_c, nTypeId));
1863 
1864     exportVaryColors(xChartType);
1865 
1866     bool bPrimaryAxes = true;
1867     exportAllSeries(xChartType, bPrimaryAxes);
1868 
1869     if( !mbIs3DChart )
1870     {
1871         // firstSliceAng
1872         exportFirstSliceAng( );
1873     }
1874 
1875     pFS->endElement( FSNS( XML_c, nTypeId ) );
1876 }
1877 
exportRadarChart(const Reference<chart2::XChartType> & xChartType)1878 void ChartExport::exportRadarChart( const Reference< chart2::XChartType >& xChartType)
1879 {
1880     FSHelperPtr pFS = GetFS();
1881     pFS->startElement(FSNS(XML_c, XML_radarChart));
1882 
1883     // radarStyle
1884     sal_Int32 eChartType = getChartType( );
1885     const char* radarStyle = nullptr;
1886     if( eChartType == chart::TYPEID_RADARAREA )
1887         radarStyle = "filled";
1888     else
1889         radarStyle = "marker";
1890     pFS->singleElement(FSNS(XML_c, XML_radarStyle), XML_val, radarStyle);
1891 
1892     exportVaryColors(xChartType);
1893     bool bPrimaryAxes = true;
1894     exportAllSeries(xChartType, bPrimaryAxes);
1895     exportAxesId(bPrimaryAxes);
1896 
1897     pFS->endElement( FSNS( XML_c, XML_radarChart ) );
1898 }
1899 
exportScatterChartSeries(const Reference<chart2::XChartType> & xChartType,css::uno::Sequence<css::uno::Reference<chart2::XDataSeries>> * pSeries)1900 void ChartExport::exportScatterChartSeries( const Reference< chart2::XChartType >& xChartType,
1901         css::uno::Sequence<css::uno::Reference<chart2::XDataSeries>>* pSeries)
1902 {
1903     FSHelperPtr pFS = GetFS();
1904     pFS->startElement(FSNS(XML_c, XML_scatterChart));
1905     // TODO:scatterStyle
1906 
1907     sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
1908     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
1909     if( GetProperty( xPropSet, "SymbolType" ) )
1910         mAny >>= nSymbolType;
1911 
1912     const char* scatterStyle = "lineMarker";
1913     if (nSymbolType == css::chart::ChartSymbolType::NONE)
1914     {
1915         scatterStyle = "line";
1916     }
1917 
1918     pFS->singleElement(FSNS(XML_c, XML_scatterStyle), XML_val, scatterStyle);
1919 
1920     exportVaryColors(xChartType);
1921     // FIXME: should export xVal and yVal
1922     bool bPrimaryAxes = true;
1923     if (pSeries)
1924         exportSeries(xChartType, *pSeries, bPrimaryAxes);
1925     exportAxesId(bPrimaryAxes);
1926 
1927     pFS->endElement( FSNS( XML_c, XML_scatterChart ) );
1928 }
1929 
exportScatterChart(const Reference<chart2::XChartType> & xChartType)1930 void ChartExport::exportScatterChart( const Reference< chart2::XChartType >& xChartType )
1931 {
1932     FSHelperPtr pFS = GetFS();
1933     std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
1934     bool bExported = false;
1935     for (auto & splitDataSeries : aSplitDataSeries)
1936     {
1937         if (!splitDataSeries.hasElements())
1938             continue;
1939 
1940         bExported = true;
1941         exportScatterChartSeries(xChartType, &splitDataSeries);
1942     }
1943     if (!bExported)
1944         exportScatterChartSeries(xChartType, nullptr);
1945 }
1946 
exportStockChart(const Reference<chart2::XChartType> & xChartType)1947 void ChartExport::exportStockChart( const Reference< chart2::XChartType >& xChartType )
1948 {
1949     FSHelperPtr pFS = GetFS();
1950     pFS->startElement(FSNS(XML_c, XML_stockChart));
1951 
1952     bool bPrimaryAxes = true;
1953     Reference< chart2::XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY );
1954     if(xDSCnt.is())
1955         exportCandleStickSeries( xDSCnt->getDataSeries(), bPrimaryAxes );
1956 
1957     // export stock properties
1958     Reference< css::chart::XStatisticDisplay > xStockPropProvider( mxDiagram, uno::UNO_QUERY );
1959     if( xStockPropProvider.is())
1960     {
1961         exportHiLowLines();
1962         exportUpDownBars(xChartType);
1963     }
1964 
1965     exportAxesId(bPrimaryAxes);
1966 
1967     pFS->endElement( FSNS( XML_c, XML_stockChart ) );
1968 }
1969 
exportHiLowLines()1970 void ChartExport::exportHiLowLines()
1971 {
1972     FSHelperPtr pFS = GetFS();
1973     // export the chart property
1974     Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
1975 
1976     if (!xChartPropProvider.is())
1977         return;
1978 
1979     Reference< beans::XPropertySet > xStockPropSet = xChartPropProvider->getMinMaxLine();
1980     if( !xStockPropSet.is() )
1981         return;
1982 
1983     pFS->startElement(FSNS(XML_c, XML_hiLowLines));
1984     exportShapeProps( xStockPropSet );
1985     pFS->endElement( FSNS( XML_c, XML_hiLowLines ) );
1986 }
1987 
exportUpDownBars(const Reference<chart2::XChartType> & xChartType)1988 void ChartExport::exportUpDownBars( const Reference< chart2::XChartType >& xChartType)
1989 {
1990     if(xChartType->getChartType() != "com.sun.star.chart2.CandleStickChartType")
1991         return;
1992 
1993     FSHelperPtr pFS = GetFS();
1994     // export the chart property
1995     Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
1996     if(xChartPropProvider.is())
1997     {
1998         //  updownbar
1999         pFS->startElement(FSNS(XML_c, XML_upDownBars));
2000         // TODO: gapWidth
2001         pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(150));
2002 
2003         Reference< beans::XPropertySet > xChartPropSet = xChartPropProvider->getUpBar();
2004         if( xChartPropSet.is() )
2005         {
2006             pFS->startElement(FSNS(XML_c, XML_upBars));
2007             // For Linechart with UpDownBars, spPr is not getting imported
2008             // so no need to call the exportShapeProps() for LineChart
2009             if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
2010             {
2011                 exportShapeProps(xChartPropSet);
2012             }
2013             pFS->endElement( FSNS( XML_c, XML_upBars ) );
2014         }
2015         xChartPropSet = xChartPropProvider->getDownBar();
2016         if( xChartPropSet.is() )
2017         {
2018             pFS->startElement(FSNS(XML_c, XML_downBars));
2019             if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
2020             {
2021                 exportShapeProps(xChartPropSet);
2022             }
2023             pFS->endElement( FSNS( XML_c, XML_downBars ) );
2024         }
2025         pFS->endElement( FSNS( XML_c, XML_upDownBars ) );
2026     }
2027 }
2028 
exportSurfaceChart(const Reference<chart2::XChartType> & xChartType)2029 void ChartExport::exportSurfaceChart( const Reference< chart2::XChartType >& xChartType )
2030 {
2031     FSHelperPtr pFS = GetFS();
2032     sal_Int32 nTypeId = XML_surfaceChart;
2033     if( mbIs3DChart )
2034         nTypeId = XML_surface3DChart;
2035     pFS->startElement(FSNS(XML_c, nTypeId));
2036     exportVaryColors(xChartType);
2037     bool bPrimaryAxes = true;
2038     exportAllSeries(xChartType, bPrimaryAxes);
2039     exportAxesId(bPrimaryAxes);
2040 
2041     pFS->endElement( FSNS( XML_c, nTypeId ) );
2042 }
2043 
exportAllSeries(const Reference<chart2::XChartType> & xChartType,bool & rPrimaryAxes)2044 void ChartExport::exportAllSeries(const Reference<chart2::XChartType>& xChartType, bool& rPrimaryAxes)
2045 {
2046     Reference< chart2::XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY );
2047     if( ! xDSCnt.is())
2048         return;
2049 
2050     // export dataseries for current chart-type
2051     Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries());
2052     exportSeries(xChartType, aSeriesSeq, rPrimaryAxes);
2053 }
2054 
exportVaryColors(const Reference<chart2::XChartType> & xChartType)2055 void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& xChartType)
2056 {
2057     FSHelperPtr pFS = GetFS();
2058     try
2059     {
2060         Reference<chart2::XDataSeries> xDataSeries = getPrimaryDataSeries(xChartType);
2061         Reference<beans::XPropertySet> xDataSeriesProps(xDataSeries, uno::UNO_QUERY_THROW);
2062         Any aAnyVaryColors = xDataSeriesProps->getPropertyValue("VaryColorsByPoint");
2063         bool bVaryColors = false;
2064         aAnyVaryColors >>= bVaryColors;
2065         pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, ToPsz10(bVaryColors));
2066     }
2067     catch (...)
2068     {
2069         pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
2070     }
2071 }
2072 
exportSeries(const Reference<chart2::XChartType> & xChartType,Sequence<Reference<chart2::XDataSeries>> & rSeriesSeq,bool & rPrimaryAxes)2073 void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType,
2074         Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq, bool& rPrimaryAxes )
2075 {
2076     OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel();
2077     OUString aChartType( xChartType->getChartType());
2078     sal_Int32 eChartType = lcl_getChartType( aChartType );
2079 
2080     for( const auto& rSeries : std::as_const(rSeriesSeq) )
2081     {
2082         // export series
2083         Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY );
2084         if( xSource.is())
2085         {
2086             Reference< chart2::XDataSeries > xDataSeries( xSource, uno::UNO_QUERY );
2087             Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
2088                 xSource->getDataSequences());
2089             // search for main sequence and create a series element
2090             {
2091                 sal_Int32 nMainSequenceIndex = -1;
2092                 sal_Int32 nSeriesLength = 0;
2093                 Reference< chart2::data::XDataSequence > xValuesSeq;
2094                 Reference< chart2::data::XDataSequence > xLabelSeq;
2095                 sal_Int32 nSeqIdx=0;
2096                 for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx )
2097                 {
2098                     OUString aRole;
2099                     Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() );
2100                     if( nMainSequenceIndex==-1 )
2101                     {
2102                         Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY );
2103                         if( xSeqProp.is())
2104                             xSeqProp->getPropertyValue("Role") >>= aRole;
2105                         // "main" sequence
2106                         if( aRole == aLabelRole )
2107                         {
2108                             xValuesSeq.set( xTempValueSeq );
2109                             xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel());
2110                             nMainSequenceIndex = nSeqIdx;
2111                         }
2112                     }
2113                     sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0));
2114                     if( nSeriesLength < nSequenceLength )
2115                         nSeriesLength = nSequenceLength;
2116                 }
2117 
2118                 // have found the main sequence, then xValuesSeq and
2119                 // xLabelSeq contain those.  Otherwise both are empty
2120                 {
2121                     FSHelperPtr pFS = GetFS();
2122 
2123                     pFS->startElement(FSNS(XML_c, XML_ser));
2124 
2125                     // TODO: idx and order
2126                     pFS->singleElement( FSNS( XML_c, XML_idx ),
2127                         XML_val, OString::number(mnSeriesCount) );
2128                     pFS->singleElement( FSNS( XML_c, XML_order ),
2129                         XML_val, OString::number(mnSeriesCount++) );
2130 
2131                     // export label
2132                     if( xLabelSeq.is() )
2133                         exportSeriesText( xLabelSeq );
2134 
2135                     Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW);
2136                     if( GetProperty( xPropSet, "AttachedAxisIndex") )
2137                     {
2138                         sal_Int32 nLocalAttachedAxis = 0;
2139                         mAny >>= nLocalAttachedAxis;
2140                         rPrimaryAxes = isPrimaryAxes(nLocalAttachedAxis);
2141                     }
2142 
2143                     // export shape properties
2144                     Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet(
2145                         rSeries, getModel() );
2146                     if( xOldPropSet.is() )
2147                     {
2148                         exportShapeProps( xOldPropSet );
2149                     }
2150 
2151                     switch( eChartType )
2152                     {
2153                         case chart::TYPEID_BUBBLE:
2154                         case chart::TYPEID_HORBAR:
2155                         case chart::TYPEID_BAR:
2156                         {
2157                             pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
2158                         }
2159                         break;
2160                         case chart::TYPEID_LINE:
2161                         {
2162                             exportMarker(xOldPropSet);
2163                             break;
2164                         }
2165                         case chart::TYPEID_PIE:
2166                         case chart::TYPEID_DOUGHNUT:
2167                         {
2168                             if( xOldPropSet.is() && GetProperty( xOldPropSet, "SegmentOffset") )
2169                             {
2170                                 sal_Int32 nOffset = 0;
2171                                 mAny >>= nOffset;
2172                                 pFS->singleElement( FSNS( XML_c, XML_explosion ),
2173                                     XML_val, OString::number( nOffset ) );
2174                             }
2175                             break;
2176                         }
2177                         case chart::TYPEID_SCATTER:
2178                         {
2179                             exportMarker(xOldPropSet);
2180                             break;
2181                         }
2182                         case chart::TYPEID_RADARLINE:
2183                         {
2184                             exportMarker(xOldPropSet);
2185                             break;
2186                         }
2187                     }
2188 
2189                     // export data points
2190                     exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType );
2191 
2192                     // export data labels
2193                     exportDataLabels(rSeries, nSeriesLength, eChartType);
2194 
2195                     exportTrendlines( rSeries );
2196 
2197                     if( eChartType != chart::TYPEID_PIE &&
2198                             eChartType != chart::TYPEID_RADARLINE )
2199                     {
2200                         //export error bars here
2201                         Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY );
2202                         Reference< XPropertySet > xErrorBarYProps;
2203                         xSeriesPropSet->getPropertyValue("ErrorBarY") >>= xErrorBarYProps;
2204                         if(xErrorBarYProps.is())
2205                             exportErrorBar(xErrorBarYProps, true);
2206                         if (eChartType != chart::TYPEID_BAR &&
2207                                 eChartType != chart::TYPEID_HORBAR)
2208                         {
2209                             Reference< XPropertySet > xErrorBarXProps;
2210                             xSeriesPropSet->getPropertyValue("ErrorBarX") >>= xErrorBarXProps;
2211                             if(xErrorBarXProps.is())
2212                                 exportErrorBar(xErrorBarXProps, false);
2213                         }
2214                     }
2215 
2216                     // export categories
2217                     if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() )
2218                         exportSeriesCategory( mxCategoriesValues );
2219 
2220                     if( (eChartType == chart::TYPEID_SCATTER)
2221                         || (eChartType == chart::TYPEID_BUBBLE) )
2222                     {
2223                         // export xVal
2224                         Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-x" ) );
2225                         if( xSequence.is() )
2226                         {
2227                             Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
2228                             if( xValues.is() )
2229                                 exportSeriesValues( xValues, XML_xVal );
2230                         }
2231                     }
2232 
2233                     if( eChartType == chart::TYPEID_BUBBLE )
2234                     {
2235                         // export yVal
2236                         Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-y" ) );
2237                         if( xSequence.is() )
2238                         {
2239                             Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
2240                             if( xValues.is() )
2241                                 exportSeriesValues( xValues, XML_yVal );
2242                         }
2243                     }
2244 
2245                     // export values
2246                     if( xValuesSeq.is() )
2247                     {
2248                         sal_Int32 nYValueType = XML_val;
2249                         if( eChartType == chart::TYPEID_SCATTER )
2250                             nYValueType = XML_yVal;
2251                         else if( eChartType == chart::TYPEID_BUBBLE )
2252                             nYValueType = XML_bubbleSize;
2253                         exportSeriesValues( xValuesSeq, nYValueType );
2254                     }
2255 
2256                     if( eChartType == chart::TYPEID_SCATTER
2257                             || eChartType == chart::TYPEID_LINE )
2258                         exportSmooth();
2259 
2260                     // tdf103988: "corrupted" files with Bubble chart opening in MSO
2261                     if( eChartType == chart::TYPEID_BUBBLE )
2262                         pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0");
2263 
2264                     pFS->endElement( FSNS( XML_c, XML_ser ) );
2265                 }
2266             }
2267         }
2268     }
2269 }
2270 
exportCandleStickSeries(const Sequence<Reference<chart2::XDataSeries>> & aSeriesSeq,bool & rPrimaryAxes)2271 void ChartExport::exportCandleStickSeries(
2272     const Sequence< Reference< chart2::XDataSeries > > & aSeriesSeq,
2273     bool& rPrimaryAxes)
2274 {
2275     for( const Reference< chart2::XDataSeries >& xSeries : aSeriesSeq )
2276     {
2277         rPrimaryAxes = lcl_isSeriesAttachedToFirstAxis(xSeries);
2278 
2279         Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY );
2280         if( xSource.is())
2281         {
2282             // export series in correct order (as we don't store roles)
2283             // with japanese candlesticks: open, low, high, close
2284             // otherwise: low, high, close
2285             Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
2286                 xSource->getDataSequences());
2287 
2288             const char* sSeries[] = {"values-first","values-max","values-min","values-last",nullptr};
2289 
2290             for( sal_Int32 idx = 0; sSeries[idx] != nullptr ; idx++ )
2291             {
2292                 Reference< chart2::data::XLabeledDataSequence > xLabeledSeq( lcl_getDataSequenceByRole( aSeqCnt, OUString::createFromAscii(sSeries[idx]) ) );
2293                 if( xLabeledSeq.is())
2294                 {
2295                     Reference< chart2::data::XDataSequence > xLabelSeq( xLabeledSeq->getLabel());
2296                     Reference< chart2::data::XDataSequence > xValueSeq( xLabeledSeq->getValues());
2297                     {
2298                         FSHelperPtr pFS = GetFS();
2299                         pFS->startElement(FSNS(XML_c, XML_ser));
2300 
2301                         // TODO: idx and order
2302                         // idx attribute should start from 1 and not from 0.
2303                         pFS->singleElement( FSNS( XML_c, XML_idx ),
2304                                 XML_val, OString::number(idx+1) );
2305                         pFS->singleElement( FSNS( XML_c, XML_order ),
2306                                 XML_val, OString::number(idx+1) );
2307 
2308                         // export label
2309                         if( xLabelSeq.is() )
2310                             exportSeriesText( xLabelSeq );
2311 
2312                         // TODO:export shape properties
2313 
2314                         // export categories
2315                         if( mxCategoriesValues.is() )
2316                             exportSeriesCategory( mxCategoriesValues );
2317 
2318                         // export values
2319                         if( xValueSeq.is() )
2320                             exportSeriesValues( xValueSeq );
2321 
2322                         pFS->endElement( FSNS( XML_c, XML_ser ) );
2323                     }
2324                 }
2325             }
2326         }
2327     }
2328 }
2329 
exportSeriesText(const Reference<chart2::data::XDataSequence> & xValueSeq)2330 void ChartExport::exportSeriesText( const Reference< chart2::data::XDataSequence > & xValueSeq )
2331 {
2332     FSHelperPtr pFS = GetFS();
2333     pFS->startElement(FSNS(XML_c, XML_tx));
2334 
2335     OUString aCellRange =  xValueSeq->getSourceRangeRepresentation();
2336     aCellRange = parseFormula( aCellRange );
2337     pFS->startElement(FSNS(XML_c, XML_strRef));
2338 
2339     pFS->startElement(FSNS(XML_c, XML_f));
2340     pFS->writeEscaped( aCellRange );
2341     pFS->endElement( FSNS( XML_c, XML_f ) );
2342 
2343     OUString aLabelString = lcl_flattenStringSequence(lcl_getLabelSequence(xValueSeq));
2344     pFS->startElement(FSNS(XML_c, XML_strCache));
2345     pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, "1");
2346     pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, "0");
2347     pFS->startElement(FSNS(XML_c, XML_v));
2348     pFS->writeEscaped( aLabelString );
2349     pFS->endElement( FSNS( XML_c, XML_v ) );
2350     pFS->endElement( FSNS( XML_c, XML_pt ) );
2351     pFS->endElement( FSNS( XML_c, XML_strCache ) );
2352     pFS->endElement( FSNS( XML_c, XML_strRef ) );
2353     pFS->endElement( FSNS( XML_c, XML_tx ) );
2354 }
2355 
exportSeriesCategory(const Reference<chart2::data::XDataSequence> & xValueSeq)2356 void ChartExport::exportSeriesCategory( const Reference< chart2::data::XDataSequence > & xValueSeq )
2357 {
2358     FSHelperPtr pFS = GetFS();
2359     pFS->startElement(FSNS(XML_c, XML_cat));
2360 
2361     OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
2362     const Sequence< Sequence< OUString >> aFinalSplitSource = getSplitCategoriesList(aCellRange);
2363     aCellRange = parseFormula( aCellRange );
2364 
2365     if(aFinalSplitSource.getLength() > 1)
2366     {
2367         // export multi level category axis labels
2368         pFS->startElement(FSNS(XML_c, XML_multiLvlStrRef));
2369 
2370         pFS->startElement(FSNS(XML_c, XML_f));
2371         pFS->writeEscaped(aCellRange);
2372         pFS->endElement(FSNS(XML_c, XML_f));
2373 
2374         pFS->startElement(FSNS(XML_c, XML_multiLvlStrCache));
2375         pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(aFinalSplitSource[0].getLength()));
2376         for(const auto& rSeq : aFinalSplitSource)
2377         {
2378             pFS->startElement(FSNS(XML_c, XML_lvl));
2379             for(sal_Int32 j = 0; j < rSeq.getLength(); j++)
2380             {
2381                 if(!rSeq[j].isEmpty())
2382                 {
2383                     pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(j));
2384                     pFS->startElement(FSNS(XML_c, XML_v));
2385                     pFS->writeEscaped(rSeq[j]);
2386                     pFS->endElement(FSNS(XML_c, XML_v));
2387                     pFS->endElement(FSNS(XML_c, XML_pt));
2388                 }
2389             }
2390             pFS->endElement(FSNS(XML_c, XML_lvl));
2391         }
2392 
2393         pFS->endElement(FSNS(XML_c, XML_multiLvlStrCache));
2394         pFS->endElement(FSNS(XML_c, XML_multiLvlStrRef));
2395     }
2396     else
2397     {
2398         // export single category axis labels
2399         pFS->startElement(FSNS(XML_c, XML_strRef));
2400 
2401         pFS->startElement(FSNS(XML_c, XML_f));
2402         pFS->writeEscaped(aCellRange);
2403         pFS->endElement(FSNS(XML_c, XML_f));
2404 
2405         ::std::vector< OUString > aCategories;
2406         lcl_fillCategoriesIntoStringVector(xValueSeq, aCategories);
2407         sal_Int32 ptCount = aCategories.size();
2408         pFS->startElement(FSNS(XML_c, XML_strCache));
2409         pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
2410         for (sal_Int32 i = 0; i < ptCount; i++)
2411         {
2412             pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
2413             pFS->startElement(FSNS(XML_c, XML_v));
2414             pFS->writeEscaped(aCategories[i]);
2415             pFS->endElement(FSNS(XML_c, XML_v));
2416             pFS->endElement(FSNS(XML_c, XML_pt));
2417         }
2418 
2419         pFS->endElement(FSNS(XML_c, XML_strCache));
2420         pFS->endElement(FSNS(XML_c, XML_strRef));
2421     }
2422 
2423     pFS->endElement( FSNS( XML_c, XML_cat ) );
2424 }
2425 
exportSeriesValues(const Reference<chart2::data::XDataSequence> & xValueSeq,sal_Int32 nValueType)2426 void ChartExport::exportSeriesValues( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType )
2427 {
2428     FSHelperPtr pFS = GetFS();
2429     pFS->startElement(FSNS(XML_c, nValueType));
2430 
2431     OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
2432     aCellRange = parseFormula( aCellRange );
2433     // TODO: need to handle XML_multiLvlStrRef according to aCellRange
2434     pFS->startElement(FSNS(XML_c, XML_numRef));
2435 
2436     pFS->startElement(FSNS(XML_c, XML_f));
2437     pFS->writeEscaped( aCellRange );
2438     pFS->endElement( FSNS( XML_c, XML_f ) );
2439 
2440     ::std::vector< double > aValues = lcl_getAllValuesFromSequence( xValueSeq );
2441     sal_Int32 ptCount = aValues.size();
2442     pFS->startElement(FSNS(XML_c, XML_numCache));
2443     pFS->startElement(FSNS(XML_c, XML_formatCode));
2444     // TODO: what format code?
2445     pFS->writeEscaped( "General" );
2446     pFS->endElement( FSNS( XML_c, XML_formatCode ) );
2447     pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
2448 
2449     for( sal_Int32 i = 0; i < ptCount; i++ )
2450     {
2451         if (!rtl::math::isNan(aValues[i]))
2452         {
2453             pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
2454             pFS->startElement(FSNS(XML_c, XML_v));
2455             pFS->write(aValues[i]);
2456             pFS->endElement(FSNS(XML_c, XML_v));
2457             pFS->endElement(FSNS(XML_c, XML_pt));
2458         }
2459     }
2460 
2461     pFS->endElement( FSNS( XML_c, XML_numCache ) );
2462     pFS->endElement( FSNS( XML_c, XML_numRef ) );
2463     pFS->endElement( FSNS( XML_c, nValueType ) );
2464 }
2465 
exportShapeProps(const Reference<XPropertySet> & xPropSet)2466 void ChartExport::exportShapeProps( const Reference< XPropertySet >& xPropSet )
2467 {
2468     FSHelperPtr pFS = GetFS();
2469     pFS->startElement(FSNS(XML_c, XML_spPr));
2470 
2471     exportFill( xPropSet );
2472     WriteOutline( xPropSet, getModel() );
2473 
2474     pFS->endElement( FSNS( XML_c, XML_spPr ) );
2475 }
2476 
exportTextProps(const Reference<XPropertySet> & xPropSet)2477 void ChartExport::exportTextProps(const Reference<XPropertySet>& xPropSet)
2478 {
2479     FSHelperPtr pFS = GetFS();
2480     pFS->startElement(FSNS(XML_c, XML_txPr));
2481 
2482     sal_Int32 nRotation = 0;
2483     if (auto xServiceInfo = uno::Reference<lang::XServiceInfo>(xPropSet, uno::UNO_QUERY))
2484     {
2485         double fMultiplier = 0;
2486         // We have at least two possible units of returned value: degrees (e.g., for data labels),
2487         // and 100ths of degree (e.g., for axes labels). The latter is returned as an Any wrapping
2488         // a sal_Int32 value (see WrappedTextRotationProperty::convertInnerToOuterValue), while
2489         // the former is double. So we could test the contained type to decide which multiplier to
2490         // use. But testing the service info should be more robust.
2491         if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxis"))
2492             fMultiplier = -600.0;
2493         else if (xServiceInfo->supportsService("com.sun.star.chart2.DataSeries"))
2494             fMultiplier = -60000.0;
2495 
2496         if (fMultiplier)
2497         {
2498             double fTextRotation = 0.0;
2499             uno::Any aAny = xPropSet->getPropertyValue("TextRotation");
2500             if (aAny.hasValue() && (aAny >>= fTextRotation))
2501             {
2502                 // The MS Office UI allows values only in range of [-90,90].
2503                 if (fTextRotation > 9000.0 && fTextRotation < 27000.0)
2504                 {
2505                     // Reflect the angle if the value is between 90° and 270°
2506                     fTextRotation -= 18000.0;
2507                 }
2508                 else if (fTextRotation >=27000.0)
2509                 {
2510                     fTextRotation -= 36000.0;
2511                 }
2512                 nRotation = std::round(fTextRotation * fMultiplier);
2513             }
2514         }
2515     }
2516 
2517     if (nRotation)
2518         pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_rot, OString::number(nRotation));
2519     else
2520         pFS->singleElement(FSNS(XML_a, XML_bodyPr));
2521 
2522     pFS->singleElement(FSNS(XML_a, XML_lstStyle));
2523 
2524     pFS->startElement(FSNS(XML_a, XML_p));
2525     pFS->startElement(FSNS(XML_a, XML_pPr));
2526 
2527     WriteRunProperties(xPropSet, false, XML_defRPr, true, o3tl::temporary(false),
2528                        o3tl::temporary(sal_Int32()));
2529 
2530     pFS->endElement(FSNS(XML_a, XML_pPr));
2531     pFS->endElement(FSNS(XML_a, XML_p));
2532     pFS->endElement(FSNS(XML_c, XML_txPr));
2533 }
2534 
InitPlotArea()2535 void ChartExport::InitPlotArea( )
2536 {
2537     Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
2538 
2539     //    Check for supported services and then the properties provided by this service.
2540     Reference<lang::XServiceInfo> xServiceInfo (mxDiagram, uno::UNO_QUERY);
2541     if (xServiceInfo.is())
2542     {
2543         if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxisZSupplier"))
2544         {
2545             xDiagramProperties->getPropertyValue("HasZAxis") >>= mbHasZAxis;
2546         }
2547     }
2548 
2549     xDiagramProperties->getPropertyValue("Dim3D") >>=  mbIs3DChart;
2550 
2551     if( mbHasCategoryLabels && mxNewDiagram.is())
2552     {
2553         Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( mxNewDiagram ) );
2554         if( xCategories.is() )
2555         {
2556             mxCategoriesValues.set( xCategories->getValues() );
2557         }
2558     }
2559 }
2560 
exportAxes()2561 void ChartExport::exportAxes( )
2562 {
2563     sal_Int32 nSize = maAxes.size();
2564     // let's export the axis types in the right order
2565     for ( sal_Int32 nSortIdx = AXIS_PRIMARY_X; nSortIdx <= AXIS_SECONDARY_Y; nSortIdx++ )
2566     {
2567         for ( sal_Int32 nIdx = 0; nIdx < nSize; nIdx++ )
2568         {
2569             if (nSortIdx == maAxes[nIdx].nAxisType)
2570                 exportAxis( maAxes[nIdx] );
2571         }
2572     }
2573 }
2574 
2575 namespace {
2576 
getXAxisType(sal_Int32 eChartType)2577 sal_Int32 getXAxisType(sal_Int32 eChartType)
2578 {
2579     if( (eChartType == chart::TYPEID_SCATTER)
2580             || (eChartType == chart::TYPEID_BUBBLE) )
2581         return  XML_valAx;
2582     else if( eChartType == chart::TYPEID_STOCK )
2583         return  XML_dateAx;
2584 
2585     return XML_catAx;
2586 }
2587 
2588 }
2589 
exportAxis(const AxisIdPair & rAxisIdPair)2590 void ChartExport::exportAxis(const AxisIdPair& rAxisIdPair)
2591 {
2592     // get some properties from document first
2593     bool bHasXAxisTitle = false,
2594          bHasYAxisTitle = false,
2595          bHasZAxisTitle = false,
2596          bHasSecondaryXAxisTitle = false,
2597          bHasSecondaryYAxisTitle = false;
2598     bool bHasXAxisMajorGrid = false,
2599          bHasXAxisMinorGrid = false,
2600          bHasYAxisMajorGrid = false,
2601          bHasYAxisMinorGrid = false,
2602          bHasZAxisMajorGrid = false,
2603          bHasZAxisMinorGrid = false;
2604 
2605     Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
2606 
2607     xDiagramProperties->getPropertyValue("HasXAxisTitle") >>= bHasXAxisTitle;
2608     xDiagramProperties->getPropertyValue("HasYAxisTitle") >>= bHasYAxisTitle;
2609     xDiagramProperties->getPropertyValue("HasZAxisTitle") >>= bHasZAxisTitle;
2610     xDiagramProperties->getPropertyValue("HasSecondaryXAxisTitle") >>=  bHasSecondaryXAxisTitle;
2611     xDiagramProperties->getPropertyValue("HasSecondaryYAxisTitle") >>=  bHasSecondaryYAxisTitle;
2612 
2613     xDiagramProperties->getPropertyValue("HasXAxisGrid") >>=  bHasXAxisMajorGrid;
2614     xDiagramProperties->getPropertyValue("HasYAxisGrid") >>=  bHasYAxisMajorGrid;
2615     xDiagramProperties->getPropertyValue("HasZAxisGrid") >>=  bHasZAxisMajorGrid;
2616 
2617     xDiagramProperties->getPropertyValue("HasXAxisHelpGrid") >>=  bHasXAxisMinorGrid;
2618     xDiagramProperties->getPropertyValue("HasYAxisHelpGrid") >>=  bHasYAxisMinorGrid;
2619     xDiagramProperties->getPropertyValue("HasZAxisHelpGrid") >>=  bHasZAxisMinorGrid;
2620 
2621     Reference< XPropertySet > xAxisProp;
2622     Reference< drawing::XShape > xAxisTitle;
2623     Reference< beans::XPropertySet > xMajorGrid;
2624     Reference< beans::XPropertySet > xMinorGrid;
2625     sal_Int32 nAxisType = XML_catAx;
2626     const char* sAxPos = nullptr;
2627 
2628     switch( rAxisIdPair.nAxisType )
2629     {
2630         case AXIS_PRIMARY_X:
2631         {
2632             Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
2633             if( xAxisXSupp.is())
2634                 xAxisProp = xAxisXSupp->getXAxis();
2635             if( bHasXAxisTitle )
2636                 xAxisTitle = xAxisXSupp->getXAxisTitle();
2637             if( bHasXAxisMajorGrid )
2638                 xMajorGrid = xAxisXSupp->getXMainGrid();
2639             if( bHasXAxisMinorGrid )
2640                 xMinorGrid = xAxisXSupp->getXHelpGrid();
2641 
2642             sal_Int32 eChartType = getChartType();
2643             nAxisType = getXAxisType(eChartType);
2644             // FIXME: axPos, need to check axis direction
2645             sAxPos = "b";
2646             break;
2647         }
2648         case AXIS_PRIMARY_Y:
2649         {
2650             Reference< css::chart::XAxisYSupplier > xAxisYSupp( mxDiagram, uno::UNO_QUERY );
2651             if( xAxisYSupp.is())
2652                 xAxisProp = xAxisYSupp->getYAxis();
2653             if( bHasYAxisTitle )
2654                 xAxisTitle = xAxisYSupp->getYAxisTitle();
2655             if( bHasYAxisMajorGrid )
2656                 xMajorGrid = xAxisYSupp->getYMainGrid();
2657             if( bHasYAxisMinorGrid )
2658                 xMinorGrid = xAxisYSupp->getYHelpGrid();
2659 
2660             nAxisType = XML_valAx;
2661             // FIXME: axPos, need to check axis direction
2662             sAxPos = "l";
2663             break;
2664         }
2665         case AXIS_PRIMARY_Z:
2666         {
2667             Reference< css::chart::XAxisZSupplier > xAxisZSupp( mxDiagram, uno::UNO_QUERY );
2668             if( xAxisZSupp.is())
2669                 xAxisProp = xAxisZSupp->getZAxis();
2670             if( bHasZAxisTitle )
2671                 xAxisTitle = xAxisZSupp->getZAxisTitle();
2672             if( bHasZAxisMajorGrid )
2673                 xMajorGrid = xAxisZSupp->getZMainGrid();
2674             if( bHasZAxisMinorGrid )
2675                 xMinorGrid = xAxisZSupp->getZHelpGrid();
2676 
2677             sal_Int32 eChartType = getChartType( );
2678             if( (eChartType == chart::TYPEID_SCATTER)
2679                 || (eChartType == chart::TYPEID_BUBBLE) )
2680                 nAxisType = XML_valAx;
2681             else if( eChartType == chart::TYPEID_STOCK )
2682                 nAxisType = XML_dateAx;
2683             else if( eChartType == chart::TYPEID_BAR )
2684                 nAxisType = XML_serAx;
2685             // FIXME: axPos, need to check axis direction
2686             sAxPos = "b";
2687             break;
2688         }
2689         case AXIS_SECONDARY_X:
2690         {
2691             Reference< css::chart::XTwoAxisXSupplier > xAxisTwoXSupp( mxDiagram, uno::UNO_QUERY );
2692             if( xAxisTwoXSupp.is())
2693                 xAxisProp = xAxisTwoXSupp->getSecondaryXAxis();
2694             if( bHasSecondaryXAxisTitle )
2695             {
2696                 Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
2697                 xAxisTitle = xAxisSupp->getSecondXAxisTitle();
2698             }
2699 
2700             sal_Int32 eChartType = getChartType();
2701             nAxisType = getXAxisType(eChartType);
2702             // FIXME: axPos, need to check axis direction
2703             sAxPos = "t";
2704             break;
2705         }
2706         case AXIS_SECONDARY_Y:
2707         {
2708             Reference< css::chart::XTwoAxisYSupplier > xAxisTwoYSupp( mxDiagram, uno::UNO_QUERY );
2709             if( xAxisTwoYSupp.is())
2710                 xAxisProp = xAxisTwoYSupp->getSecondaryYAxis();
2711             if( bHasSecondaryYAxisTitle )
2712             {
2713                 Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
2714                 xAxisTitle = xAxisSupp->getSecondYAxisTitle();
2715             }
2716 
2717             nAxisType = XML_valAx;
2718             // FIXME: axPos, need to check axis direction
2719             sAxPos = "r";
2720             break;
2721         }
2722     }
2723 
2724     _exportAxis(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType, sAxPos, rAxisIdPair);
2725 }
2726 
_exportAxis(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)2727 void ChartExport::_exportAxis(
2728     const Reference< XPropertySet >& xAxisProp,
2729     const Reference< drawing::XShape >& xAxisTitle,
2730     const Reference< XPropertySet >& xMajorGrid,
2731     const Reference< XPropertySet >& xMinorGrid,
2732     sal_Int32 nAxisType,
2733     const char* sAxisPos,
2734     const AxisIdPair& rAxisIdPair )
2735 {
2736     FSHelperPtr pFS = GetFS();
2737     pFS->startElement(FSNS(XML_c, nAxisType));
2738     pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(rAxisIdPair.nAxisId));
2739 
2740     pFS->startElement(FSNS(XML_c, XML_scaling));
2741 
2742     // logBase, min, max
2743     if(GetProperty( xAxisProp, "Logarithmic" ) )
2744     {
2745         bool bLogarithmic = false;
2746         mAny >>= bLogarithmic;
2747         if( bLogarithmic )
2748         {
2749             // default value is 10?
2750             pFS->singleElement(FSNS(XML_c, XML_logBase), XML_val, OString::number(10));
2751         }
2752     }
2753 
2754     // orientation: minMax, maxMin
2755     bool bReverseDirection = false;
2756     if(GetProperty( xAxisProp, "ReverseDirection" ) )
2757         mAny >>= bReverseDirection;
2758 
2759     const char* orientation = bReverseDirection ? "maxMin":"minMax";
2760     pFS->singleElement(FSNS(XML_c, XML_orientation), XML_val, orientation);
2761 
2762     bool bAutoMax = false;
2763     if(GetProperty( xAxisProp, "AutoMax" ) )
2764         mAny >>= bAutoMax;
2765 
2766     if( !bAutoMax && (GetProperty( xAxisProp, "Max" ) ) )
2767     {
2768         double dMax = 0;
2769         mAny >>= dMax;
2770         pFS->singleElement(FSNS(XML_c, XML_max), XML_val, OString::number(dMax));
2771     }
2772 
2773     bool bAutoMin = false;
2774     if(GetProperty( xAxisProp, "AutoMin" ) )
2775         mAny >>= bAutoMin;
2776 
2777     if( !bAutoMin && (GetProperty( xAxisProp, "Min" ) ) )
2778     {
2779         double dMin = 0;
2780         mAny >>= dMin;
2781         pFS->singleElement(FSNS(XML_c, XML_min), XML_val, OString::number(dMin));
2782     }
2783 
2784     pFS->endElement( FSNS( XML_c, XML_scaling ) );
2785 
2786     bool bVisible = true;
2787     if( xAxisProp.is() )
2788     {
2789         xAxisProp->getPropertyValue("Visible") >>=  bVisible;
2790     }
2791 
2792     // only export each axis only once non-deleted
2793     bool bDeleted = maExportedAxis.find(rAxisIdPair.nAxisType) != maExportedAxis.end();
2794 
2795     if (!bDeleted)
2796         maExportedAxis.insert(rAxisIdPair.nAxisType);
2797 
2798     pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, !bDeleted && bVisible ? "0" : "1");
2799 
2800     // FIXME: axPos, need to check the property "ReverseDirection"
2801     pFS->singleElement(FSNS(XML_c, XML_axPos), XML_val, sAxisPos);
2802     // major grid line
2803     if( xMajorGrid.is())
2804     {
2805         pFS->startElement(FSNS(XML_c, XML_majorGridlines));
2806         exportShapeProps( xMajorGrid );
2807         pFS->endElement( FSNS( XML_c, XML_majorGridlines ) );
2808     }
2809 
2810     // minor grid line
2811     if( xMinorGrid.is())
2812     {
2813         pFS->startElement(FSNS(XML_c, XML_minorGridlines));
2814         exportShapeProps( xMinorGrid );
2815         pFS->endElement( FSNS( XML_c, XML_minorGridlines ) );
2816     }
2817 
2818     // title
2819     if( xAxisTitle.is() )
2820         exportTitle( xAxisTitle );
2821 
2822     bool bLinkedNumFmt = true;
2823     if (GetProperty(xAxisProp, "LinkNumberFormatToSource"))
2824         mAny >>= bLinkedNumFmt;
2825 
2826     OUString aNumberFormatString("General");
2827     if (GetProperty(xAxisProp, "NumberFormat"))
2828     {
2829         sal_Int32 nKey = 0;
2830         mAny >>= nKey;
2831         aNumberFormatString = getNumberFormatCode(nKey);
2832     }
2833 
2834     OString sNumberFormatString = OUStringToOString(aNumberFormatString, RTL_TEXTENCODING_UTF8);
2835     pFS->singleElement(FSNS(XML_c, XML_numFmt),
2836             XML_formatCode, sNumberFormatString.getStr(),
2837             XML_sourceLinked, bLinkedNumFmt ? "1" : "0");
2838 
2839     // majorTickMark
2840     sal_Int32 nValue = 0;
2841     if(GetProperty( xAxisProp, "Marks" ) )
2842     {
2843         mAny >>= nValue;
2844         bool bInner = nValue & css::chart::ChartAxisMarks::INNER;
2845         bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER;
2846         const char* majorTickMark = nullptr;
2847         if( bInner && bOuter )
2848             majorTickMark = "cross";
2849         else if( bInner )
2850             majorTickMark = "in";
2851         else if( bOuter )
2852             majorTickMark = "out";
2853         else
2854             majorTickMark = "none";
2855         pFS->singleElement(FSNS(XML_c, XML_majorTickMark), XML_val, majorTickMark);
2856     }
2857     // minorTickMark
2858     if(GetProperty( xAxisProp, "HelpMarks" ) )
2859     {
2860         mAny >>= nValue;
2861         bool bInner = nValue & css::chart::ChartAxisMarks::INNER;
2862         bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER;
2863         const char* minorTickMark = nullptr;
2864         if( bInner && bOuter )
2865             minorTickMark = "cross";
2866         else if( bInner )
2867             minorTickMark = "in";
2868         else if( bOuter )
2869             minorTickMark = "out";
2870         else
2871             minorTickMark = "none";
2872         pFS->singleElement(FSNS(XML_c, XML_minorTickMark), XML_val, minorTickMark);
2873     }
2874     // tickLblPos
2875     const char* sTickLblPos = nullptr;
2876     bool bDisplayLabel = true;
2877     if(GetProperty( xAxisProp, "DisplayLabels" ) )
2878         mAny >>= bDisplayLabel;
2879     if( bDisplayLabel && (GetProperty( xAxisProp, "LabelPosition" ) ) )
2880     {
2881         css::chart::ChartAxisLabelPosition eLabelPosition = css::chart::ChartAxisLabelPosition_NEAR_AXIS;
2882         mAny >>= eLabelPosition;
2883         switch( eLabelPosition )
2884         {
2885             case css::chart::ChartAxisLabelPosition_NEAR_AXIS:
2886             case css::chart::ChartAxisLabelPosition_NEAR_AXIS_OTHER_SIDE:
2887                 sTickLblPos = "nextTo";
2888                 break;
2889             case css::chart::ChartAxisLabelPosition_OUTSIDE_START:
2890                 sTickLblPos = "low";
2891                 break;
2892             case css::chart::ChartAxisLabelPosition_OUTSIDE_END:
2893                 sTickLblPos = "high";
2894                 break;
2895             default:
2896                 sTickLblPos = "nextTo";
2897                 break;
2898         }
2899     }
2900     else
2901     {
2902         sTickLblPos = "none";
2903     }
2904     pFS->singleElement(FSNS(XML_c, XML_tickLblPos), XML_val, sTickLblPos);
2905 
2906     // shape properties
2907     exportShapeProps( xAxisProp );
2908 
2909     exportTextProps(xAxisProp);
2910 
2911     pFS->singleElement(FSNS(XML_c, XML_crossAx), XML_val, OString::number(rAxisIdPair.nCrossAx));
2912 
2913     // crosses & crossesAt
2914     bool bCrossesValue = false;
2915     const char* sCrosses = nullptr;
2916     // do not export the CrossoverPosition/CrossoverValue, if the axis is deleted and not visible
2917     if( GetProperty( xAxisProp, "CrossoverPosition" ) && !bDeleted && bVisible )
2918     {
2919         css::chart::ChartAxisPosition ePosition( css::chart::ChartAxisPosition_ZERO );
2920         mAny >>= ePosition;
2921         switch( ePosition )
2922         {
2923             case css::chart::ChartAxisPosition_START:
2924                 sCrosses = "min";
2925                 break;
2926             case css::chart::ChartAxisPosition_END:
2927                 sCrosses = "max";
2928                 break;
2929             case css::chart::ChartAxisPosition_ZERO:
2930                 sCrosses = "autoZero";
2931                 break;
2932             default:
2933                 bCrossesValue = true;
2934                 break;
2935         }
2936     }
2937 
2938     if( bCrossesValue && GetProperty( xAxisProp, "CrossoverValue" ) )
2939     {
2940         double dValue = 0;
2941         mAny >>= dValue;
2942         pFS->singleElement(FSNS(XML_c, XML_crossesAt), XML_val, OString::number(dValue));
2943     }
2944     else
2945     {
2946         if(sCrosses)
2947         {
2948             pFS->singleElement(FSNS(XML_c, XML_crosses), XML_val, sCrosses);
2949         }
2950     }
2951 
2952     if( ( nAxisType == XML_catAx )
2953         || ( nAxisType == XML_dateAx ) )
2954     {
2955         // FIXME: seems not support? use default value,
2956         const char* const isAuto = "1";
2957         pFS->singleElement(FSNS(XML_c, XML_auto), XML_val, isAuto);
2958 
2959         if( nAxisType == XML_catAx )
2960         {
2961             // FIXME: seems not support? lblAlgn
2962             const char* const sLblAlgn = "ctr";
2963             pFS->singleElement(FSNS(XML_c, XML_lblAlgn), XML_val, sLblAlgn);
2964         }
2965 
2966         // FIXME: seems not support? lblOffset
2967         pFS->singleElement(FSNS(XML_c, XML_lblOffset), XML_val, OString::number(100));
2968 
2969         // FIXME: seems not support? noMultiLvlLbl
2970         pFS->singleElement(FSNS(XML_c, XML_noMultiLvlLbl), XML_val, OString::number(0));
2971     }
2972 
2973     // crossBetween
2974     if( nAxisType == XML_valAx )
2975     {
2976         if( mbIsCategoryPositionShifted )
2977             pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "between");
2978         else
2979             pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "midCat");
2980     }
2981 
2982     // majorUnit
2983     bool bAutoStepMain = false;
2984     if(GetProperty( xAxisProp, "AutoStepMain" ) )
2985         mAny >>= bAutoStepMain;
2986 
2987     if( !bAutoStepMain && (GetProperty( xAxisProp, "StepMain" ) ) )
2988     {
2989         double dMajorUnit = 0;
2990         mAny >>= dMajorUnit;
2991         pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(dMajorUnit));
2992     }
2993     // minorUnit
2994     bool bAutoStepHelp = false;
2995     if(GetProperty( xAxisProp, "AutoStepHelp" ) )
2996         mAny >>= bAutoStepHelp;
2997 
2998     if( !bAutoStepHelp && (GetProperty( xAxisProp, "StepHelp" ) ) )
2999     {
3000         double dMinorUnit = 0;
3001         mAny >>= dMinorUnit;
3002         if( GetProperty( xAxisProp, "StepHelpCount" ) )
3003         {
3004             sal_Int32 dMinorUnitCount = 0;
3005             mAny >>= dMinorUnitCount;
3006             // tdf#114168 Don't save minor unit if number of step help count is 5 (which is default for MS Excel),
3007             // to allow proper .xlsx import. If minorUnit is set and majorUnit not, then it is impossible
3008             // to calculate StepHelpCount.
3009             if( dMinorUnitCount != 5 )
3010             {
3011                 pFS->singleElement( FSNS( XML_c, XML_minorUnit ),
3012                     XML_val, OString::number( dMinorUnit ) );
3013             }
3014         }
3015     }
3016 
3017     if( nAxisType == XML_valAx && GetProperty( xAxisProp, "DisplayUnits" ) )
3018     {
3019         bool bDisplayUnits = false;
3020         mAny >>= bDisplayUnits;
3021         if(bDisplayUnits)
3022         {
3023             OUString aVal;
3024             if(GetProperty( xAxisProp, "BuiltInUnit" ))
3025             {
3026                 mAny >>= aVal;
3027                 if(!aVal.isEmpty())
3028                 {
3029                     pFS->startElement(FSNS(XML_c, XML_dispUnits));
3030 
3031                     pFS->singleElement(FSNS(XML_c, XML_builtInUnit), XML_val, aVal.toUtf8());
3032 
3033                     pFS->singleElement(FSNS( XML_c, XML_dispUnitsLbl ));
3034                     pFS->endElement( FSNS( XML_c, XML_dispUnits ) );
3035                 }
3036              }
3037         }
3038     }
3039 
3040     pFS->endElement( FSNS( XML_c, nAxisType ) );
3041 }
3042 
3043 namespace {
3044 
3045 struct LabelPlacementParam
3046 {
3047     bool mbExport;
3048     sal_Int32 meDefault;
3049 
3050     std::unordered_set<sal_Int32> maAllowedValues;
3051 
LabelPlacementParamoox::drawingml::__anona986c9d00711::LabelPlacementParam3052     LabelPlacementParam() :
3053         mbExport(true),
3054         meDefault(css::chart::DataLabelPlacement::OUTSIDE) {}
3055 
allowAlloox::drawingml::__anona986c9d00711::LabelPlacementParam3056     void allowAll()
3057     {
3058         maAllowedValues.insert(css::chart::DataLabelPlacement::OUTSIDE);
3059         maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
3060         maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
3061         maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
3062         maAllowedValues.insert(css::chart::DataLabelPlacement::TOP);
3063         maAllowedValues.insert(css::chart::DataLabelPlacement::BOTTOM);
3064         maAllowedValues.insert(css::chart::DataLabelPlacement::LEFT);
3065         maAllowedValues.insert(css::chart::DataLabelPlacement::RIGHT);
3066         maAllowedValues.insert(css::chart::DataLabelPlacement::AVOID_OVERLAP);
3067     }
3068 };
3069 
toOOXMLPlacement(sal_Int32 nPlacement)3070 const char* toOOXMLPlacement( sal_Int32 nPlacement )
3071 {
3072     switch (nPlacement)
3073     {
3074         case css::chart::DataLabelPlacement::OUTSIDE:       return "outEnd";
3075         case css::chart::DataLabelPlacement::INSIDE:        return "inEnd";
3076         case css::chart::DataLabelPlacement::CENTER:        return "ctr";
3077         case css::chart::DataLabelPlacement::NEAR_ORIGIN:   return "inBase";
3078         case css::chart::DataLabelPlacement::TOP:           return "t";
3079         case css::chart::DataLabelPlacement::BOTTOM:        return "b";
3080         case css::chart::DataLabelPlacement::LEFT:          return "l";
3081         case css::chart::DataLabelPlacement::RIGHT:         return "r";
3082         case css::chart::DataLabelPlacement::AVOID_OVERLAP: return "bestFit";
3083         default:
3084             ;
3085     }
3086 
3087     return "outEnd";
3088 }
3089 
getFieldTypeString(const chart2::DataPointCustomLabelFieldType aType)3090 OUString getFieldTypeString( const chart2::DataPointCustomLabelFieldType aType )
3091 {
3092     switch (aType)
3093     {
3094     case chart2::DataPointCustomLabelFieldType_CATEGORYNAME:
3095         return "CATEGORYNAME";
3096 
3097     case chart2::DataPointCustomLabelFieldType_SERIESNAME:
3098         return "SERIESNAME";
3099 
3100     case chart2::DataPointCustomLabelFieldType_VALUE:
3101         return "VALUE";
3102 
3103     case chart2::DataPointCustomLabelFieldType_CELLREF:
3104         return "CELLREF";
3105 
3106     default:
3107         break;
3108     }
3109     return OUString();
3110 }
3111 
writeRunProperties(ChartExport * pChartExport,Reference<XPropertySet> const & xPropertySet)3112 void writeRunProperties( ChartExport* pChartExport, Reference<XPropertySet> const & xPropertySet )
3113 {
3114     bool bDummy = false;
3115     sal_Int32 nDummy;
3116     pChartExport->WriteRunProperties(xPropertySet, false, XML_rPr, true, bDummy, nDummy);
3117 }
3118 
writeCustomLabel(const FSHelperPtr & pFS,ChartExport * pChartExport,const Sequence<Reference<chart2::XDataPointCustomLabelField>> & rCustomLabelFields)3119 void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport,
3120                        const Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields )
3121 {
3122     pFS->startElement(FSNS(XML_c, XML_tx));
3123     pFS->startElement(FSNS(XML_c, XML_rich));
3124 
3125     // TODO: body properties?
3126     pFS->singleElement(FSNS(XML_a, XML_bodyPr));
3127 
3128     OUString sFieldType;
3129     pFS->startElement(FSNS(XML_a, XML_p));
3130 
3131     for (auto& rField : rCustomLabelFields)
3132     {
3133         Reference<XPropertySet> xPropertySet(rField, UNO_QUERY);
3134         chart2::DataPointCustomLabelFieldType aType = rField->getFieldType();
3135         sFieldType.clear();
3136         bool bNewParagraph = false;
3137 
3138         if (aType == chart2::DataPointCustomLabelFieldType_NEWLINE)
3139             bNewParagraph = true;
3140         else if (aType != chart2::DataPointCustomLabelFieldType_TEXT)
3141             sFieldType = getFieldTypeString(aType);
3142 
3143         if (bNewParagraph)
3144         {
3145             pFS->endElement(FSNS(XML_a, XML_p));
3146             pFS->startElement(FSNS(XML_a, XML_p));
3147             continue;
3148         }
3149 
3150         if (sFieldType.isEmpty())
3151         {
3152             // Normal text run
3153             pFS->startElement(FSNS(XML_a, XML_r));
3154             writeRunProperties(pChartExport, xPropertySet);
3155 
3156             pFS->startElement(FSNS(XML_a, XML_t));
3157             pFS->writeEscaped(rField->getString());
3158             pFS->endElement(FSNS(XML_a, XML_t));
3159 
3160             pFS->endElement(FSNS(XML_a, XML_r));
3161         }
3162         else
3163         {
3164             // Field
3165             pFS->startElement(FSNS(XML_a, XML_fld), XML_id, rField->getGuid().toUtf8(), XML_type,
3166                               sFieldType.toUtf8());
3167             writeRunProperties(pChartExport, xPropertySet);
3168 
3169             pFS->startElement(FSNS(XML_a, XML_t));
3170             pFS->writeEscaped(rField->getString());
3171             pFS->endElement(FSNS(XML_a, XML_t));
3172 
3173             pFS->endElement(FSNS(XML_a, XML_fld));
3174         }
3175     }
3176 
3177     pFS->endElement(FSNS(XML_a, XML_p));
3178     pFS->endElement(FSNS(XML_c, XML_rich));
3179     pFS->endElement(FSNS(XML_c, XML_tx));
3180 }
3181 
writeLabelProperties(const FSHelperPtr & pFS,ChartExport * pChartExport,const uno::Reference<beans::XPropertySet> & xPropSet,const LabelPlacementParam & rLabelParam)3182 void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport,
3183     const uno::Reference<beans::XPropertySet>& xPropSet, const LabelPlacementParam& rLabelParam )
3184 {
3185     if (!xPropSet.is())
3186         return;
3187 
3188     chart2::DataPointLabel aLabel;
3189     Sequence<Reference<chart2::XDataPointCustomLabelField>> aCustomLabelFields;
3190     sal_Int32 nLabelBorderWidth = 0;
3191     sal_Int32 nLabelBorderColor = 0x00FFFFFF;
3192 
3193     xPropSet->getPropertyValue("Label") >>= aLabel;
3194     xPropSet->getPropertyValue("CustomLabelFields") >>= aCustomLabelFields;
3195     xPropSet->getPropertyValue("LabelBorderWidth") >>= nLabelBorderWidth;
3196     xPropSet->getPropertyValue("LabelBorderColor") >>= nLabelBorderColor;
3197 
3198     if (nLabelBorderWidth > 0)
3199     {
3200         pFS->startElement(FSNS(XML_c, XML_spPr));
3201         pFS->startElement(FSNS(XML_a, XML_ln), XML_w,
3202                           OString::number(convertHmmToEmu(nLabelBorderWidth)));
3203         if (nLabelBorderColor != -1)
3204         {
3205             pFS->startElement(FSNS(XML_a, XML_solidFill));
3206 
3207             OString aStr = OString::number(nLabelBorderColor, 16).toAsciiUpperCase();
3208             pFS->singleElement(FSNS(XML_a, XML_srgbClr), XML_val, aStr);
3209 
3210             pFS->endElement(FSNS(XML_a, XML_solidFill));
3211         }
3212         pFS->endElement(FSNS(XML_a, XML_ln));
3213         pFS->endElement(FSNS(XML_c, XML_spPr));
3214     }
3215 
3216     if (aCustomLabelFields.hasElements())
3217         writeCustomLabel(pFS, pChartExport, aCustomLabelFields);
3218 
3219     if (rLabelParam.mbExport)
3220     {
3221         sal_Int32 nLabelPlacement = rLabelParam.meDefault;
3222         if (xPropSet->getPropertyValue("LabelPlacement") >>= nLabelPlacement)
3223         {
3224             if (!rLabelParam.maAllowedValues.count(nLabelPlacement))
3225                 nLabelPlacement = rLabelParam.meDefault;
3226             pFS->singleElement(FSNS(XML_c, XML_dLblPos), XML_val, toOOXMLPlacement(nLabelPlacement));
3227         }
3228     }
3229 
3230     pFS->singleElement(FSNS(XML_c, XML_showLegendKey), XML_val, ToPsz10(aLabel.ShowLegendSymbol));
3231     pFS->singleElement(FSNS(XML_c, XML_showVal), XML_val, ToPsz10(aLabel.ShowNumber));
3232     pFS->singleElement(FSNS(XML_c, XML_showCatName), XML_val, ToPsz10(aLabel.ShowCategoryName));
3233     pFS->singleElement(FSNS(XML_c, XML_showSerName), XML_val, ToPsz10(false));
3234     pFS->singleElement(FSNS(XML_c, XML_showPercent), XML_val, ToPsz10(aLabel.ShowNumberInPercent));
3235 
3236     // Export the text "separator" if exists
3237     uno::Any aAny = xPropSet->getPropertyValue("LabelSeparator");
3238     if( aAny.hasValue() )
3239     {
3240         OUString nLabelSeparator;
3241         aAny >>= nLabelSeparator;
3242         pFS->startElement(FSNS(XML_c, XML_separator));
3243         pFS->writeEscaped( nLabelSeparator );
3244         pFS->endElement( FSNS( XML_c, XML_separator ) );
3245     }
3246 }
3247 
3248 }
3249 
exportDataLabels(const uno::Reference<chart2::XDataSeries> & xSeries,sal_Int32 nSeriesLength,sal_Int32 eChartType)3250 void ChartExport::exportDataLabels(
3251     const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType )
3252 {
3253     if (!xSeries.is() || nSeriesLength <= 0)
3254         return;
3255 
3256     uno::Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
3257     if (!xPropSet.is())
3258         return;
3259 
3260     FSHelperPtr pFS = GetFS();
3261     pFS->startElement(FSNS(XML_c, XML_dLbls));
3262 
3263     bool bLinkedNumFmt = true;
3264     if (GetProperty(xPropSet, "LinkNumberFormatToSource"))
3265         mAny >>= bLinkedNumFmt;
3266 
3267     chart2::DataPointLabel aLabel;
3268     bool bLabelIsNumberFormat = true;
3269     if( xPropSet->getPropertyValue("Label") >>= aLabel )
3270         bLabelIsNumberFormat = aLabel.ShowNumber;
3271 
3272     if (GetProperty(xPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat")))
3273     {
3274         sal_Int32 nKey = 0;
3275         mAny >>= nKey;
3276 
3277         OUString aNumberFormatString = getNumberFormatCode(nKey);
3278         OString sNumberFormatString = OUStringToOString(aNumberFormatString, RTL_TEXTENCODING_UTF8);
3279 
3280         pFS->singleElement(FSNS(XML_c, XML_numFmt),
3281             XML_formatCode, sNumberFormatString,
3282             XML_sourceLinked, ToPsz10(bLinkedNumFmt));
3283     }
3284 
3285     uno::Sequence<sal_Int32> aAttrLabelIndices;
3286     xPropSet->getPropertyValue("AttributedDataPoints") >>= aAttrLabelIndices;
3287 
3288     // We must not export label placement property when the chart type doesn't
3289     // support this option in MS Office, else MS Office would think the file
3290     // is corrupt & refuse to open it.
3291 
3292     const chart::TypeGroupInfo& rInfo = chart::GetTypeGroupInfo(static_cast<chart::TypeId>(eChartType));
3293     LabelPlacementParam aParam;
3294     aParam.mbExport = !mbIs3DChart;
3295     aParam.meDefault = rInfo.mnDefLabelPos;
3296     aParam.allowAll();
3297     switch (eChartType) // diagram chart type
3298     {
3299         case chart::TYPEID_PIE:
3300             if(getChartType() == chart::TYPEID_DOUGHNUT)
3301                 aParam.mbExport = false;
3302             else
3303             // All pie charts support label placement.
3304             aParam.mbExport = true;
3305         break;
3306         case chart::TYPEID_AREA:
3307         case chart::TYPEID_RADARLINE:
3308         case chart::TYPEID_RADARAREA:
3309             // These chart types don't support label placement.
3310             aParam.mbExport = false;
3311         break;
3312         case chart::TYPEID_BAR:
3313             if (mbStacked || mbPercent)
3314             {
3315                 aParam.maAllowedValues.clear();
3316                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
3317                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
3318                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
3319                 aParam.meDefault = css::chart::DataLabelPlacement::CENTER;
3320             }
3321             else  // Clustered bar chart
3322             {
3323                 aParam.maAllowedValues.clear();
3324                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
3325                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
3326                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::OUTSIDE);
3327                 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
3328                 aParam.meDefault = css::chart::DataLabelPlacement::OUTSIDE;
3329             }
3330         break;
3331         default:
3332             ;
3333     }
3334 
3335     for (const sal_Int32 nIdx : std::as_const(aAttrLabelIndices))
3336     {
3337         uno::Reference<beans::XPropertySet> xLabelPropSet = xSeries->getDataPointByIndex(nIdx);
3338 
3339         if (!xLabelPropSet.is())
3340             continue;
3341 
3342         pFS->startElement(FSNS(XML_c, XML_dLbl));
3343         pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nIdx));
3344 
3345         if( GetProperty(xLabelPropSet, "LinkNumberFormatToSource") )
3346             mAny >>= bLinkedNumFmt;
3347 
3348         if( xLabelPropSet->getPropertyValue("Label") >>= aLabel )
3349             bLabelIsNumberFormat = aLabel.ShowNumber;
3350         else
3351             bLabelIsNumberFormat = true;
3352 
3353         if (GetProperty(xLabelPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat")))
3354         {
3355             sal_Int32 nKey = 0;
3356             mAny >>= nKey;
3357 
3358             OUString aNumberFormatString = getNumberFormatCode(nKey);
3359             OString sNumberFormatString = OUStringToOString(aNumberFormatString, RTL_TEXTENCODING_UTF8);
3360 
3361             pFS->singleElement(FSNS(XML_c, XML_numFmt), XML_formatCode, sNumberFormatString.getStr(),
3362                                XML_sourceLinked, ToPsz10(bLinkedNumFmt));
3363         }
3364 
3365         // Individual label property that overwrites the baseline.
3366         exportTextProps( xLabelPropSet );
3367         writeLabelProperties(pFS, this, xLabelPropSet, aParam);
3368         pFS->endElement(FSNS(XML_c, XML_dLbl));
3369     }
3370 
3371     exportTextProps( xPropSet );
3372     // Baseline label properties for all labels.
3373     writeLabelProperties(pFS, this, xPropSet, aParam);
3374 
3375     pFS->singleElement(FSNS(XML_c, XML_showLeaderLines), XML_val, "0");
3376 
3377     pFS->endElement(FSNS(XML_c, XML_dLbls));
3378 }
3379 
exportDataPoints(const uno::Reference<beans::XPropertySet> & xSeriesProperties,sal_Int32 nSeriesLength,sal_Int32 eChartType)3380 void ChartExport::exportDataPoints(
3381     const uno::Reference< beans::XPropertySet > & xSeriesProperties,
3382     sal_Int32 nSeriesLength, sal_Int32 eChartType )
3383 {
3384     uno::Reference< chart2::XDataSeries > xSeries( xSeriesProperties, uno::UNO_QUERY );
3385     bool bVaryColorsByPoint = false;
3386     Sequence< sal_Int32 > aDataPointSeq;
3387     if( xSeriesProperties.is())
3388     {
3389         Any aAny = xSeriesProperties->getPropertyValue( "AttributedDataPoints" );
3390         aAny >>= aDataPointSeq;
3391         xSeriesProperties->getPropertyValue( "VaryColorsByPoint" ) >>= bVaryColorsByPoint;
3392     }
3393 
3394     const sal_Int32 * pPoints = aDataPointSeq.getConstArray();
3395     sal_Int32 nElement;
3396     Reference< chart2::XColorScheme > xColorScheme;
3397     if( mxNewDiagram.is())
3398         xColorScheme.set( mxNewDiagram->getDefaultColorScheme());
3399 
3400     if( bVaryColorsByPoint && xColorScheme.is() )
3401     {
3402         ::std::set< sal_Int32 > aAttrPointSet;
3403         ::std::copy( pPoints, pPoints + aDataPointSeq.getLength(),
3404                     ::std::inserter( aAttrPointSet, aAttrPointSet.begin()));
3405         const ::std::set< sal_Int32 >::const_iterator aEndIt( aAttrPointSet.end());
3406         for( nElement = 0; nElement < nSeriesLength; ++nElement )
3407         {
3408             uno::Reference< beans::XPropertySet > xPropSet;
3409             if( aAttrPointSet.find( nElement ) != aEndIt )
3410             {
3411                 try
3412                 {
3413                     xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
3414                             xSeries, nElement, getModel() );
3415                 }
3416                 catch( const uno::Exception & )
3417                 {
3418                     DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
3419                 }
3420             }
3421             else
3422             {
3423                 // property set only containing the color
3424                 xPropSet.set( new ColorPropertySet( xColorScheme->getColorByIndex( nElement )));
3425             }
3426 
3427             if( xPropSet.is() )
3428             {
3429                 FSHelperPtr pFS = GetFS();
3430                 pFS->startElement(FSNS(XML_c, XML_dPt));
3431                 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
3432 
3433                 switch (eChartType)
3434                 {
3435                     case chart::TYPEID_PIE:
3436                     case chart::TYPEID_DOUGHNUT:
3437                     {
3438                         if( xPropSet.is() && GetProperty( xPropSet, "SegmentOffset") )
3439                         {
3440                             sal_Int32 nOffset = 0;
3441                             mAny >>= nOffset;
3442                             if (nOffset)
3443                                 pFS->singleElement( FSNS( XML_c, XML_explosion ),
3444                                         XML_val, OString::number( nOffset ) );
3445                         }
3446                         break;
3447                     }
3448                     default:
3449                         break;
3450                 }
3451                 exportShapeProps( xPropSet );
3452 
3453                 pFS->endElement( FSNS( XML_c, XML_dPt ) );
3454             }
3455         }
3456     }
3457 
3458     // Export Data Point Property in Charts even if the VaryColors is false
3459     if( !bVaryColorsByPoint )
3460     {
3461         ::std::set< sal_Int32 > aAttrPointSet;
3462         ::std::copy( pPoints, pPoints + aDataPointSeq.getLength(),
3463                     ::std::inserter( aAttrPointSet, aAttrPointSet.begin()));
3464         const ::std::set< sal_Int32 >::const_iterator aEndIt( aAttrPointSet.end());
3465         for( nElement = 0; nElement < nSeriesLength; ++nElement )
3466         {
3467             uno::Reference< beans::XPropertySet > xPropSet;
3468             if( aAttrPointSet.find( nElement ) != aEndIt )
3469             {
3470                 try
3471                 {
3472                     xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
3473                             xSeries, nElement, getModel() );
3474                 }
3475                 catch( const uno::Exception & )
3476                 {
3477                     DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
3478                 }
3479             }
3480 
3481             if( xPropSet.is() )
3482             {
3483                 FSHelperPtr pFS = GetFS();
3484                 pFS->startElement(FSNS(XML_c, XML_dPt));
3485                 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
3486 
3487                 switch( eChartType )
3488                 {
3489                     case chart::TYPEID_BUBBLE:
3490                     case chart::TYPEID_HORBAR:
3491                     case chart::TYPEID_BAR:
3492                         pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
3493                         exportShapeProps(xPropSet);
3494                         break;
3495 
3496                     case chart::TYPEID_LINE:
3497                     case chart::TYPEID_SCATTER:
3498                     case chart::TYPEID_RADARLINE:
3499                         exportMarker(xPropSet);
3500                         break;
3501 
3502                     default:
3503                         exportShapeProps(xPropSet);
3504                         break;
3505                 }
3506 
3507                 pFS->endElement( FSNS( XML_c, XML_dPt ) );
3508             }
3509         }
3510     }
3511 }
3512 
exportAxesId(bool bPrimaryAxes,bool bCheckCombinedAxes)3513 void ChartExport::exportAxesId(bool bPrimaryAxes, bool bCheckCombinedAxes)
3514 {
3515     sal_Int32 nAxisIdx, nAxisIdy;
3516     bool bPrimaryAxisExists = false;
3517     bool bSecondaryAxisExists = false;
3518     // let's check which axis already exists and which axis is attached to the actual dataseries
3519     if (maAxes.size() >= 2)
3520     {
3521         bPrimaryAxisExists = bPrimaryAxes && maAxes[1].nAxisType == AXIS_PRIMARY_Y;
3522         bSecondaryAxisExists = !bPrimaryAxes && maAxes[1].nAxisType == AXIS_SECONDARY_Y;
3523     }
3524     // tdf#114181 keep axes of combined charts
3525     if ( bCheckCombinedAxes && ( bPrimaryAxisExists || bSecondaryAxisExists ) )
3526     {
3527         nAxisIdx = maAxes[0].nAxisId;
3528         nAxisIdy = maAxes[1].nAxisId;
3529     }
3530     else
3531     {
3532         nAxisIdx = lcl_generateRandomValue();
3533         nAxisIdy = lcl_generateRandomValue();
3534         AxesType eXAxis = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X;
3535         AxesType eYAxis = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y;
3536         maAxes.emplace_back( eXAxis, nAxisIdx, nAxisIdy );
3537         maAxes.emplace_back( eYAxis, nAxisIdy, nAxisIdx );
3538     }
3539     FSHelperPtr pFS = GetFS();
3540     pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdx));
3541     pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdy));
3542     if (mbHasZAxis)
3543     {
3544         sal_Int32 nAxisIdz = 0;
3545         if( isDeep3dChart() )
3546         {
3547             nAxisIdz = lcl_generateRandomValue();
3548             maAxes.emplace_back( AXIS_PRIMARY_Z, nAxisIdz, nAxisIdy );
3549         }
3550         pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdz));
3551     }
3552 }
3553 
exportGrouping(bool isBar)3554 void ChartExport::exportGrouping( bool isBar )
3555 {
3556     FSHelperPtr pFS = GetFS();
3557     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
3558     // grouping
3559     if( GetProperty( xPropSet, "Stacked" ) )
3560         mAny >>= mbStacked;
3561     if( GetProperty( xPropSet, "Percent" ) )
3562         mAny >>= mbPercent;
3563 
3564     const char* grouping = nullptr;
3565     if (mbStacked)
3566         grouping = "stacked";
3567     else if (mbPercent)
3568         grouping = "percentStacked";
3569     else
3570     {
3571         if( isBar && !isDeep3dChart() )
3572         {
3573             grouping = "clustered";
3574         }
3575         else
3576             grouping = "standard";
3577     }
3578     pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, grouping);
3579 }
3580 
exportTrendlines(const Reference<chart2::XDataSeries> & xSeries)3581 void ChartExport::exportTrendlines( const Reference< chart2::XDataSeries >& xSeries )
3582 {
3583     FSHelperPtr pFS = GetFS();
3584     Reference< chart2::XRegressionCurveContainer > xRegressionCurveContainer( xSeries, UNO_QUERY );
3585     if( xRegressionCurveContainer.is() )
3586     {
3587         const Sequence< Reference< chart2::XRegressionCurve > > aRegCurveSeq = xRegressionCurveContainer->getRegressionCurves();
3588         for( const Reference< chart2::XRegressionCurve >& xRegCurve : aRegCurveSeq )
3589         {
3590             if (!xRegCurve.is())
3591                 continue;
3592 
3593             Reference< XPropertySet > xProperties( xRegCurve , uno::UNO_QUERY );
3594 
3595             OUString aService;
3596             Reference< lang::XServiceName > xServiceName( xProperties, UNO_QUERY );
3597             if( !xServiceName.is() )
3598                 continue;
3599 
3600             aService = xServiceName->getServiceName();
3601 
3602             if(aService != "com.sun.star.chart2.LinearRegressionCurve" &&
3603                     aService != "com.sun.star.chart2.ExponentialRegressionCurve" &&
3604                     aService != "com.sun.star.chart2.LogarithmicRegressionCurve" &&
3605                     aService != "com.sun.star.chart2.PotentialRegressionCurve" &&
3606                     aService != "com.sun.star.chart2.PolynomialRegressionCurve" &&
3607                     aService != "com.sun.star.chart2.MovingAverageRegressionCurve")
3608                 continue;
3609 
3610             pFS->startElement(FSNS(XML_c, XML_trendline));
3611 
3612             OUString aName;
3613             xProperties->getPropertyValue("CurveName") >>= aName;
3614             if(!aName.isEmpty())
3615             {
3616                 pFS->startElement(FSNS(XML_c, XML_name));
3617                 pFS->writeEscaped(aName);
3618                 pFS->endElement( FSNS( XML_c, XML_name) );
3619             }
3620 
3621             exportShapeProps( xProperties );
3622 
3623             if( aService == "com.sun.star.chart2.LinearRegressionCurve" )
3624             {
3625                 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "linear");
3626             }
3627             else if( aService == "com.sun.star.chart2.ExponentialRegressionCurve" )
3628             {
3629                 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "exp");
3630             }
3631             else if( aService == "com.sun.star.chart2.LogarithmicRegressionCurve" )
3632             {
3633                 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "log");
3634             }
3635             else if( aService == "com.sun.star.chart2.PotentialRegressionCurve" )
3636             {
3637                 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "power");
3638             }
3639             else if( aService == "com.sun.star.chart2.PolynomialRegressionCurve" )
3640             {
3641                 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "poly");
3642 
3643                 sal_Int32 aDegree = 2;
3644                 xProperties->getPropertyValue( "PolynomialDegree") >>= aDegree;
3645                 pFS->singleElement(FSNS(XML_c, XML_order), XML_val, OString::number(aDegree));
3646             }
3647             else if( aService == "com.sun.star.chart2.MovingAverageRegressionCurve" )
3648             {
3649                 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "movingAvg");
3650 
3651                 sal_Int32 aPeriod = 2;
3652                 xProperties->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
3653 
3654                 pFS->singleElement(FSNS(XML_c, XML_period), XML_val, OString::number(aPeriod));
3655             }
3656             else
3657             {
3658                 // should never happen
3659                 // This would produce invalid OOXML files so we check earlier for the type
3660                 assert(false);
3661             }
3662 
3663             double fExtrapolateForward = 0.0;
3664             double fExtrapolateBackward = 0.0;
3665 
3666             xProperties->getPropertyValue("ExtrapolateForward") >>= fExtrapolateForward;
3667             xProperties->getPropertyValue("ExtrapolateBackward") >>= fExtrapolateBackward;
3668 
3669             pFS->singleElement( FSNS( XML_c, XML_forward ),
3670                     XML_val, OString::number(fExtrapolateForward) );
3671 
3672             pFS->singleElement( FSNS( XML_c, XML_backward ),
3673                     XML_val, OString::number(fExtrapolateBackward) );
3674 
3675             bool bForceIntercept = false;
3676             xProperties->getPropertyValue("ForceIntercept") >>= bForceIntercept;
3677 
3678             if (bForceIntercept)
3679             {
3680                 double fInterceptValue = 0.0;
3681                 xProperties->getPropertyValue("InterceptValue") >>= fInterceptValue;
3682 
3683                 pFS->singleElement( FSNS( XML_c, XML_intercept ),
3684                     XML_val, OString::number(fInterceptValue) );
3685             }
3686 
3687             // Equation properties
3688             Reference< XPropertySet > xEquationProperties( xRegCurve->getEquationProperties() );
3689 
3690             // Show Equation
3691             bool bShowEquation = false;
3692             xEquationProperties->getPropertyValue("ShowEquation") >>= bShowEquation;
3693 
3694             // Show R^2
3695             bool bShowCorrelationCoefficient = false;
3696             xEquationProperties->getPropertyValue("ShowCorrelationCoefficient") >>= bShowCorrelationCoefficient;
3697 
3698             pFS->singleElement( FSNS( XML_c, XML_dispRSqr ),
3699                     XML_val, ToPsz10(bShowCorrelationCoefficient) );
3700 
3701             pFS->singleElement(FSNS(XML_c, XML_dispEq), XML_val, ToPsz10(bShowEquation));
3702 
3703             pFS->endElement( FSNS( XML_c, XML_trendline ) );
3704         }
3705     }
3706 }
3707 
exportMarker(const Reference<XPropertySet> & xPropSet)3708 void ChartExport::exportMarker(const Reference< XPropertySet >& xPropSet)
3709 {
3710     chart2::Symbol aSymbol;
3711     if( GetProperty( xPropSet, "Symbol" ) )
3712         mAny >>= aSymbol;
3713 
3714     if(aSymbol.Style != chart2::SymbolStyle_STANDARD && aSymbol.Style != chart2::SymbolStyle_AUTO && aSymbol.Style != chart2::SymbolStyle_NONE)
3715         return;
3716 
3717     FSHelperPtr pFS = GetFS();
3718     pFS->startElement(FSNS(XML_c, XML_marker));
3719 
3720     sal_Int32 nSymbol = aSymbol.StandardSymbol;
3721     // TODO: more properties support for marker
3722     const char* pSymbolType; // no initialization here, to let compiler warn if we have a code path
3723                              // where it stays uninitialized
3724     switch( nSymbol )
3725     {
3726         case 0:
3727             pSymbolType = "square";
3728             break;
3729         case 1:
3730             pSymbolType = "diamond";
3731             break;
3732         case 2:
3733         case 3:
3734         case 4:
3735         case 5:
3736             pSymbolType = "triangle";
3737             break;
3738         case 8:
3739             pSymbolType = "circle";
3740             break;
3741         case 9:
3742             pSymbolType = "star";
3743             break;
3744         case 10:
3745             pSymbolType = "x"; // in MS office 2010 built in symbol marker 'X' is represented as 'x'
3746             break;
3747         case 11:
3748             pSymbolType = "plus";
3749             break;
3750         case 13:
3751             pSymbolType = "dash";
3752             break;
3753         default:
3754             pSymbolType = "square";
3755             break;
3756     }
3757 
3758     bool bSkipFormatting = false;
3759     if (aSymbol.Style == chart2::SymbolStyle_NONE)
3760     {
3761         bSkipFormatting = true;
3762         pSymbolType = "none";
3763     }
3764 
3765     pFS->singleElement(FSNS(XML_c, XML_symbol), XML_val, pSymbolType);
3766 
3767     if (!bSkipFormatting)
3768     {
3769         awt::Size aSymbolSize = aSymbol.Size;
3770         sal_Int32 nSize = std::max( aSymbolSize.Width, aSymbolSize.Height );
3771 
3772         nSize = nSize/250.0*7.0 + 1; // just guessed based on some test cases,
3773         //the value is always 1 less than the actual value.
3774         nSize = std::min<sal_Int32>( 72, std::max<sal_Int32>( 2, nSize ) );
3775         pFS->singleElement(FSNS(XML_c, XML_size), XML_val, OString::number(nSize));
3776 
3777         pFS->startElement(FSNS(XML_c, XML_spPr));
3778 
3779         util::Color aColor = aSymbol.FillColor;
3780         if (GetProperty(xPropSet, "Color"))
3781             mAny >>= aColor;
3782 
3783         if (aColor == -1)
3784         {
3785             pFS->singleElement(FSNS(XML_a, XML_noFill));
3786         }
3787         else
3788             WriteSolidFill(::Color(aColor));
3789 
3790         pFS->endElement( FSNS( XML_c, XML_spPr ) );
3791     }
3792 
3793     pFS->endElement( FSNS( XML_c, XML_marker ) );
3794 }
3795 
exportSmooth()3796 void ChartExport::exportSmooth()
3797 {
3798     FSHelperPtr pFS = GetFS();
3799     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY );
3800     sal_Int32 nSplineType = 0;
3801     if( GetProperty( xPropSet, "SplineType" ) )
3802         mAny >>= nSplineType;
3803     const char* pVal = nSplineType != 0 ? "1" : "0";
3804     pFS->singleElement(FSNS(XML_c, XML_smooth), XML_val, pVal);
3805 }
3806 
exportFirstSliceAng()3807 void ChartExport::exportFirstSliceAng( )
3808 {
3809     FSHelperPtr pFS = GetFS();
3810     sal_Int32 nStartingAngle = 0;
3811     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
3812     if( GetProperty( xPropSet, "StartingAngle" ) )
3813         mAny >>= nStartingAngle;
3814 
3815     // convert to ooxml angle
3816     nStartingAngle = (450 - nStartingAngle ) % 360;
3817     pFS->singleElement(FSNS(XML_c, XML_firstSliceAng), XML_val, OString::number(nStartingAngle));
3818 }
3819 
3820 namespace {
3821 
getErrorBarStyle(sal_Int32 nErrorBarStyle)3822 const char* getErrorBarStyle(sal_Int32 nErrorBarStyle)
3823 {
3824     switch(nErrorBarStyle)
3825     {
3826         case cssc::ErrorBarStyle::NONE:
3827             return nullptr;
3828         case cssc::ErrorBarStyle::VARIANCE:
3829             break;
3830         case cssc::ErrorBarStyle::STANDARD_DEVIATION:
3831             return "stdDev";
3832         case cssc::ErrorBarStyle::ABSOLUTE:
3833             return "fixedVal";
3834         case cssc::ErrorBarStyle::RELATIVE:
3835             return "percentage";
3836         case cssc::ErrorBarStyle::ERROR_MARGIN:
3837             break;
3838         case cssc::ErrorBarStyle::STANDARD_ERROR:
3839             return "stdErr";
3840         case cssc::ErrorBarStyle::FROM_DATA:
3841             return "cust";
3842         default:
3843             assert(false && "can't happen");
3844     }
3845     return nullptr;
3846 }
3847 
getLabeledSequence(const uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence>> & aSequences,bool bPositive)3848 Reference< chart2::data::XDataSequence>  getLabeledSequence(
3849         const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > >& aSequences,
3850         bool bPositive )
3851 {
3852     OUString aDirection;
3853     if(bPositive)
3854         aDirection = "positive";
3855     else
3856         aDirection = "negative";
3857 
3858     for( const auto& rSequence : aSequences )
3859     {
3860         if( rSequence.is())
3861         {
3862             uno::Reference< chart2::data::XDataSequence > xSequence( rSequence->getValues());
3863             uno::Reference< beans::XPropertySet > xSeqProp( xSequence, uno::UNO_QUERY_THROW );
3864             OUString aRole;
3865             if( ( xSeqProp->getPropertyValue( "Role" ) >>= aRole ) &&
3866                     aRole.match( "error-bars" ) && aRole.indexOf(aDirection) >= 0 )
3867             {
3868                 return xSequence;
3869             }
3870         }
3871     }
3872 
3873     return Reference< chart2::data::XDataSequence > ();
3874 }
3875 
3876 }
3877 
exportErrorBar(const Reference<XPropertySet> & xErrorBarProps,bool bYError)3878 void ChartExport::exportErrorBar(const Reference< XPropertySet>& xErrorBarProps, bool bYError)
3879 {
3880     sal_Int32 nErrorBarStyle = cssc::ErrorBarStyle::NONE;
3881     xErrorBarProps->getPropertyValue("ErrorBarStyle") >>= nErrorBarStyle;
3882     const char* pErrorBarStyle = getErrorBarStyle(nErrorBarStyle);
3883     if(!pErrorBarStyle)
3884         return;
3885 
3886     FSHelperPtr pFS = GetFS();
3887     pFS->startElement(FSNS(XML_c, XML_errBars));
3888     pFS->singleElement(FSNS(XML_c, XML_errDir), XML_val, bYError ? "y" : "x");
3889     bool bPositive = false, bNegative = false;
3890     xErrorBarProps->getPropertyValue("ShowPositiveError") >>= bPositive;
3891     xErrorBarProps->getPropertyValue("ShowNegativeError") >>= bNegative;
3892     const char* pErrBarType;
3893     if(bPositive && bNegative)
3894         pErrBarType = "both";
3895     else if(bPositive)
3896         pErrBarType = "plus";
3897     else if(bNegative)
3898         pErrBarType = "minus";
3899     else
3900     {
3901         // what the hell should we do now?
3902         // at least this makes the file valid
3903         pErrBarType = "both";
3904     }
3905     pFS->singleElement(FSNS(XML_c, XML_errBarType), XML_val, pErrBarType);
3906     pFS->singleElement(FSNS(XML_c, XML_errValType), XML_val, pErrorBarStyle);
3907     pFS->singleElement(FSNS(XML_c, XML_noEndCap), XML_val, "0");
3908     if(nErrorBarStyle == cssc::ErrorBarStyle::FROM_DATA)
3909     {
3910         uno::Reference< chart2::data::XDataSource > xDataSource(xErrorBarProps, uno::UNO_QUERY);
3911         Sequence< Reference < chart2::data::XLabeledDataSequence > > aSequences =
3912             xDataSource->getDataSequences();
3913 
3914         if(bPositive)
3915         {
3916             exportSeriesValues(getLabeledSequence(aSequences, true), XML_plus);
3917         }
3918 
3919         if(bNegative)
3920         {
3921             exportSeriesValues(getLabeledSequence(aSequences, false), XML_minus);
3922         }
3923     }
3924     else
3925     {
3926         double nVal = 0.0;
3927         if(nErrorBarStyle == cssc::ErrorBarStyle::STANDARD_DEVIATION)
3928         {
3929             xErrorBarProps->getPropertyValue("Weight") >>= nVal;
3930         }
3931         else
3932         {
3933             if(bPositive)
3934                 xErrorBarProps->getPropertyValue("PositiveError") >>= nVal;
3935             else
3936                 xErrorBarProps->getPropertyValue("NegativeError") >>= nVal;
3937         }
3938 
3939         pFS->singleElement(FSNS(XML_c, XML_val), XML_val, OString::number(nVal));
3940     }
3941 
3942     exportShapeProps( xErrorBarProps );
3943 
3944     pFS->endElement( FSNS( XML_c, XML_errBars) );
3945 }
3946 
exportView3D()3947 void ChartExport::exportView3D()
3948 {
3949     Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
3950     if( !xPropSet.is() )
3951         return;
3952     FSHelperPtr pFS = GetFS();
3953     pFS->startElement(FSNS(XML_c, XML_view3D));
3954     sal_Int32 eChartType = getChartType( );
3955     // rotX
3956     if( GetProperty( xPropSet, "RotationHorizontal" ) )
3957     {
3958         sal_Int32 nRotationX = 0;
3959         mAny >>= nRotationX;
3960         if( nRotationX < 0 )
3961         {
3962             if(eChartType == chart::TYPEID_PIE)
3963             {
3964             /* In OOXML we get value in 0..90 range for pie chart X rotation , whereas we expect it to be in -90..90 range,
3965                so we convert that during import. It is modified in View3DConverter::convertFromModel()
3966                here we convert it back to 0..90 as we received in import */
3967                nRotationX += 90;  // X rotation (map Chart2 [-179,180] to OOXML [0..90])
3968             }
3969             else
3970                 nRotationX += 360; // X rotation (map Chart2 [-179,180] to OOXML [-90..90])
3971         }
3972         pFS->singleElement(FSNS(XML_c, XML_rotX), XML_val, OString::number(nRotationX));
3973     }
3974     // rotY
3975     if( GetProperty( xPropSet, "RotationVertical" ) )
3976     {
3977         // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
3978         if( eChartType == chart::TYPEID_PIE && GetProperty( xPropSet, "StartingAngle" ) )
3979         {
3980          // Y rotation used as 'first pie slice angle' in 3D pie charts
3981             sal_Int32 nStartingAngle=0;
3982             mAny >>= nStartingAngle;
3983             // convert to ooxml angle
3984             nStartingAngle = (450 - nStartingAngle ) % 360;
3985             pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nStartingAngle));
3986         }
3987         else
3988         {
3989             sal_Int32 nRotationY = 0;
3990             mAny >>= nRotationY;
3991             // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
3992             if( nRotationY < 0 )
3993                 nRotationY += 360;
3994             pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nRotationY));
3995         }
3996     }
3997     // rAngAx
3998     if( GetProperty( xPropSet, "RightAngledAxes" ) )
3999     {
4000         bool bRightAngled = false;
4001         mAny >>= bRightAngled;
4002         const char* sRightAngled = bRightAngled ? "1":"0";
4003         pFS->singleElement(FSNS(XML_c, XML_rAngAx), XML_val, sRightAngled);
4004     }
4005     // perspective
4006     if( GetProperty( xPropSet, "Perspective" ) )
4007     {
4008         sal_Int32 nPerspective = 0;
4009         mAny >>= nPerspective;
4010         // map Chart2 [0,100] to OOXML [0..200]
4011         nPerspective *= 2;
4012         pFS->singleElement(FSNS(XML_c, XML_perspective), XML_val, OString::number(nPerspective));
4013     }
4014     pFS->endElement( FSNS( XML_c, XML_view3D ) );
4015 }
4016 
isDeep3dChart()4017 bool ChartExport::isDeep3dChart()
4018 {
4019     bool isDeep = false;
4020     if( mbIs3DChart )
4021     {
4022         Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
4023         if( GetProperty( xPropSet, "Deep" ) )
4024             mAny >>= isDeep;
4025     }
4026     return isDeep;
4027 }
4028 
getNumberFormatCode(sal_Int32 nKey) const4029 OUString ChartExport::getNumberFormatCode(sal_Int32 nKey) const
4030 {
4031     /* XXX if this was called more than one or two times per export the two
4032      * SvNumberFormatter instances and NfKeywordTable should be member
4033      * variables and initialized only once. */
4034 
4035     OUString aCode("General");  // init with fallback
4036     uno::Reference<util::XNumberFormatsSupplier> xNumberFormatsSupplier(mxChartModel, uno::UNO_QUERY_THROW);
4037     SvNumberFormatsSupplierObj* pSupplierObj = comphelper::getUnoTunnelImplementation<SvNumberFormatsSupplierObj>( xNumberFormatsSupplier);
4038     if (!pSupplierObj)
4039         return aCode;
4040 
4041     SvNumberFormatter* pNumberFormatter = pSupplierObj->GetNumberFormatter();
4042     if (!pNumberFormatter)
4043         return aCode;
4044 
4045     SvNumberFormatter aTempFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US);
4046     NfKeywordTable aKeywords;
4047     aTempFormatter.FillKeywordTableForExcel( aKeywords);
4048     aCode = pNumberFormatter->GetFormatStringForExcel( nKey, aKeywords, aTempFormatter);
4049 
4050     return aCode;
4051 }
4052 
4053 }// drawingml
4054 }// oox
4055 
4056 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
4057