/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <oox/token/namespaces.hxx>
#include <oox/token/properties.hxx>
#include <oox/token/tokens.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/export/chartexport.hxx>
#include <oox/token/relationship.hxx>
#include <oox/export/utils.hxx>
#include <drawingml/chart/typegroupconverter.hxx>
#include <basegfx/utils/gradienttools.hxx>
#include <docmodel/uno/UnoGradientTools.hxx>
#include <cstdio>
#include <limits>
#include <com/sun/star/awt/Gradient2.hpp>
#include <com/sun/star/chart/XChartDocument.hpp>
#include <com/sun/star/chart/ChartLegendPosition.hpp>
#include <com/sun/star/chart/XTwoAxisXSupplier.hpp>
#include <com/sun/star/chart/XTwoAxisYSupplier.hpp>
#include <com/sun/star/chart/XAxisZSupplier.hpp>
#include <com/sun/star/chart/ChartDataRowSource.hpp>
#include <com/sun/star/chart/X3DDisplay.hpp>
#include <com/sun/star/chart/XStatisticDisplay.hpp>
#include <com/sun/star/chart/XSecondAxisTitleSupplier.hpp>
#include <com/sun/star/chart/ChartSymbolType.hpp>
#include <com/sun/star/chart/ChartAxisMarks.hpp>
#include <com/sun/star/chart/ChartAxisLabelPosition.hpp>
#include <com/sun/star/chart/ChartAxisPosition.hpp>
#include <com/sun/star/chart/ChartSolidType.hpp>
#include <com/sun/star/chart/DataLabelPlacement.hpp>
#include <com/sun/star/chart/ErrorBarStyle.hpp>
#include <com/sun/star/chart/MissingValueTreatment.hpp>
#include <com/sun/star/chart/XDiagramPositioning.hpp>
#include <com/sun/star/chart/TimeIncrement.hpp>
#include <com/sun/star/chart/TimeInterval.hpp>
#include <com/sun/star/chart/TimeUnit.hpp>
#include <com/sun/star/chart2/RelativePosition.hpp>
#include <com/sun/star/chart2/RelativeSize.hpp>
#include <com/sun/star/chart2/XChartDocument.hpp>
#include <com/sun/star/chart2/XDiagram.hpp>
#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
#include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
#include <com/sun/star/chart2/XChartTypeContainer.hpp>
#include <com/sun/star/chart2/XDataSeriesContainer.hpp>
#include <com/sun/star/chart2/DataPointLabel.hpp>
#include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
#include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp>
#include <com/sun/star/chart2/PieChartSubType.hpp>
#include <com/sun/star/chart2/Symbol.hpp>
#include <com/sun/star/chart2/data/XDataSource.hpp>
#include <com/sun/star/chart2/data/XDataProvider.hpp>
#include <com/sun/star/chart2/data/XTextualDataSequence.hpp>
#include <com/sun/star/chart2/data/XNumericalDataSequence.hpp>
#include <com/sun/star/chart2/data/XLabeledDataSequence.hpp>
#include <com/sun/star/chart2/XAnyDescriptionAccess.hpp>
#include <com/sun/star/chart2/AxisType.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/drawing/XShapes.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/LineStyle.hpp>
#include <com/sun/star/awt/XBitmap.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/XServiceName.hpp>
#include <com/sun/star/table/CellAddress.hpp>
#include <com/sun/star/sheet/XFormulaParser.hpp>
#include <com/sun/star/sheet/FormulaToken.hpp>
#include <com/sun/star/sheet/AddressConvention.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/embed/XVisualObject.hpp>
#include <com/sun/star/embed/Aspects.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/random.hxx>
#include <utility>
#include <xmloff/SchXMLSeriesHelper.hxx>
#include "ColorPropertySet.hxx"
#include <svl/numformat.hxx>
#include <svl/numuno.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <sal/log.hxx>
#include <set>
#include <unordered_set>
#include <frozen/bits/defines.h>
#include <frozen/bits/elsa_std.h>
#include <frozen/unordered_map.h>
#include <o3tl/temporary.hxx>
#include <o3tl/sorted_vector.hxx>
using namespace css;
using namespace css::uno;
using namespace css::drawing;
using namespace ::oox::core;
using css::beans::PropertyValue;
using css::beans::XPropertySet;
using css::container::XNamed;
using css::table::CellAddress;
using css::sheet::XFormulaParser;
using ::oox::core::XmlFilterBase;
using ::sax_fastparser::FSHelperPtr;
namespace cssc = css::chart;
namespace oox::drawingml {
namespace {
bool isPrimaryAxes(sal_Int32 nIndex)
{
assert(nIndex == 0 || nIndex == 1);
return nIndex != 1;
}
class lcl_MatchesRole
{
public :
explicit lcl_MatchesRole( OUString aRole ) :
m_aRole(std::move( aRole ))
{}
bool operator () (
const Reference< chart2::data::XLabeledDataSequence > & xSeq )
const
{
if ( !xSeq.is() )
return false ;
Reference< beans::XPropertySet > xProp( xSeq->getValues(), uno::UNO_QUERY );
OUString aRole;
return ( xProp.is() &&
(xProp->getPropertyValue( u
"Role" _ustr ) >>= aRole ) &&
m_aRole == aRole );
}
private :
OUString m_aRole;
};
void outputStyleEntry(FSHelperPtr pFS, sal_Int32 nElTokenId)
{
// Just default values for now
pFS->startElement(FSNS(XML_cs, nElTokenId));
pFS->singleElement(FSNS(XML_cs, XML_lnRef), XML_idx,
"0" );
pFS->singleElement(FSNS(XML_cs, XML_fillRef), XML_idx,
"0" );
pFS->singleElement(FSNS(XML_cs, XML_effectRef), XML_idx,
"0" );
pFS->singleElement(FSNS(XML_cs, XML_fontRef), XML_idx,
"minor" );
pFS->endElement(FSNS(XML_cs, nElTokenId));
}
void outputChartAreaStyleEntry(FSHelperPtr pFS)
{
// Just default values for now
pFS->startElement(FSNS(XML_cs, XML_chartArea), XML_mods,
"allowNoFillOverride allowNoLineOverride" );
pFS->singleElement(FSNS(XML_cs, XML_lnRef), XML_idx,
"0" );
pFS->singleElement(FSNS(XML_cs, XML_fillRef), XML_idx,
"0" );
pFS->singleElement(FSNS(XML_cs, XML_effectRef), XML_idx,
"0" );
pFS->startElement(FSNS(XML_cs, XML_fontRef), XML_idx,
"minor" );
pFS->singleElement(FSNS(XML_a, XML_schemeClr), XML_val,
"tx1" );
pFS->endElement(FSNS(XML_cs, XML_fontRef));
pFS->startElement(FSNS(XML_cs, XML_spPr));
pFS->startElement(FSNS(XML_a, XML_solidFill));
pFS->singleElement(FSNS(XML_a, XML_schemeClr), XML_val,
"bg1" );
pFS->endElement(FSNS(XML_a, XML_solidFill));
pFS->startElement(FSNS(XML_a, XML_ln), XML_w,
"9525" , XML_cap,
"flat" ,
XML_cmpd,
"sng" , XML_algn,
"ctr" );
pFS->startElement(FSNS(XML_a, XML_solidFill));
pFS->startElement(FSNS(XML_a, XML_schemeClr), XML_val,
"tx1" );
pFS->singleElement(FSNS(XML_a, XML_lumMod), XML_val,
"15000" );
pFS->singleElement(FSNS(XML_a, XML_lumOff), XML_val,
"85000" );
pFS->endElement(FSNS(XML_a, XML_schemeClr));
pFS->endElement(FSNS(XML_a, XML_solidFill));
pFS->singleElement(FSNS(XML_a, XML_round));
pFS->endElement(FSNS(XML_a, XML_ln));
pFS->endElement(FSNS(XML_cs, XML_spPr));
pFS->endElement(FSNS(XML_cs, XML_chartArea));
}
void outputDataPointStyleEntry(FSHelperPtr pFS)
{
pFS->startElement(FSNS(XML_cs, XML_dataPoint));
pFS->singleElement(FSNS(XML_cs, XML_lnRef), XML_idx,
"0" );
pFS->startElement(FSNS(XML_cs, XML_fillRef), XML_idx,
"0" );
pFS->singleElement(FSNS(XML_cs, XML_styleClr), XML_val,
"auto" );
pFS->endElement(FSNS(XML_cs, XML_fillRef));
pFS->singleElement(FSNS(XML_cs, XML_effectRef), XML_idx,
"0" );
pFS->startElement(FSNS(XML_cs, XML_fontRef), XML_idx,
"minor" );
pFS->singleElement(FSNS(XML_cs, XML_schemeClr), XML_val,
"tx1" );
pFS->endElement(FSNS(XML_cs, XML_fontRef));
pFS->startElement(FSNS(XML_cs, XML_spPr));
pFS->startElement(FSNS(XML_a, XML_solidFill));
pFS->singleElement(FSNS(XML_a, XML_schemeClr), XML_val,
"phClr" );
pFS->endElement(FSNS(XML_a, XML_solidFill));
pFS->endElement(FSNS(XML_cs, XML_spPr));
pFS->endElement(FSNS(XML_cs, XML_dataPoint));
}
}
// unnamed namespace
static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories(
const Referenc
e< chart2::XDiagram > & xDiagram, bool & bHasDateCategories )
{
bHasDateCategories = false ;
Reference< chart2::data::XLabeledDataSequence > xResult;
try
{
Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
xDiagram, uno::UNO_QUERY_THROW );
const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
xCooSysCnt->getCoordinateSystems());
for ( const auto & xCooSys : aCooSysSeq )
{
OSL_ASSERT( xCooSys.is());
for ( sal_Int32 nN = xCooSys->getDimension(); nN--; )
{
const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nN);
for (sal_Int32 nI=0; nI<=nMaxAxisIndex; ++nI)
{
Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension( nN, nI );
OSL_ASSERT( xAxis.is());
if ( xAxis.is())
{
chart2::ScaleData aScaleData = xAxis->getScaleData();
if ( aScaleData.Categories.is())
{
bHasDateCategories = aScaleData.AxisType == chart2::AxisType::DATE;
xResult.set( aScaleData.Categories );
break ;
}
}
}
}
}
}
catch ( const uno::Exception & )
{
DBG_UNHANDLED_EXCEPTION("oox" );
}
return xResult;
}
static Reference< chart2::data::XLabeledDataSequence >
lcl_getDataSequenceByRole(
const Sequence< Reference< chart2::data::XLabeledDataSequence > > & aLabeledSeq,
const OUString & rRole )
{
Reference< chart2::data::XLabeledDataSequence > aNoResult;
const Reference< chart2::data::XLabeledDataSequence > * pBegin = aLabeledSeq.getConstArray();
const Reference< chart2::data::XLabeledDataSequence > * pEnd = pBegin + aLabeledSeq.getLength();
const Reference< chart2::data::XLabeledDataSequence > * pMatch =
::std::find_if( pBegin, pEnd, lcl_MatchesRole( rRole ));
if ( pMatch != pEnd )
return *pMatch;
return aNoResult;
}
static bool lcl_hasCategoryLabels( const Reference< chart2::XChartDocument >& xChartDoc )
{
//categories are always the first sequence
Reference< chart2::XDiagram > xDiagram( xChartDoc->getFirstDiagram());
bool bDateCategories;
Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( xDiagram, bDateCategories ) );
return xCategories.is();
}
static bool lcl_isCategoryAxisShifted( const Reference< chart2::XDiagram >& xDiagram )
{
bool bCategoryPositionShifted = false ;
try
{
Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
xDiagram, uno::UNO_QUERY_THROW);
const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
xCooSysCnt->getCoordinateSystems());
for (const auto & xCooSys : aCooSysSeq)
{
OSL_ASSERT(xCooSys.is());
if ( 0 < xCooSys->getDimension() && 0 <= xCooSys->getMaximumAxisIndexByDimension(0) )
{
Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, 0);
OSL_ASSERT(xAxis.is());
if (xAxis.is())
{
chart2::ScaleData aScaleData = xAxis->getScaleData();
bCategoryPositionShifted = aScaleData.ShiftedCategoryPosition;
break ;
}
}
}
}
catch (const uno::Exception&)
{
DBG_UNHANDLED_EXCEPTION("oox" );
}
return bCategoryPositionShifted;
}
static sal_Int32 lcl_getCategoryAxisType( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex )
{
sal_Int32 nAxisType = -1;
try
{
Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
xDiagram, uno::UNO_QUERY_THROW);
const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
xCooSysCnt->getCoordinateSystems());
for ( const auto & xCooSys : aCooSysSeq )
{
OSL_ASSERT(xCooSys.is());
if ( nDimensionIndex < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(nDimensionIndex) )
{
Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(nDimensionIndex, nAxisIndex);
OSL_ASSERT(xAxis.is());
if ( xAxis.is() )
{
chart2::ScaleData aScaleData = xAxis->getScaleData();
nAxisType = aScaleData.AxisType;
break ;
}
}
}
}
catch (const uno::Exception&)
{
DBG_UNHANDLED_EXCEPTION("oox" );
}
return nAxisType;
}
static OUString lclGetTimeUnitToken( sal_Int32 nTimeUnit )
{
switch ( nTimeUnit )
{
case cssc::TimeUnit::DAY: return u"days" _ustr;
case cssc::TimeUnit::MONTH: return u"months" _ustr;
case cssc::TimeUnit::YEAR: return u"years" _ustr;
default : OSL_ENSURE(false , "lclGetTimeUnitToken - unexpected time unit" );
}
return u"days" _ustr;
}
static cssc::TimeIncrement lcl_getDateTimeIncrement( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nAxisIndex )
{
cssc::TimeIncrement aTimeIncrement;
try
{
Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
xDiagram, uno::UNO_QUERY_THROW);
const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
xCooSysCnt->getCoordinateSystems());
for ( const auto & xCooSys : aCooSysSeq )
{
OSL_ASSERT(xCooSys.is());
if ( 0 < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(0) )
{
Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, nAxisIndex);
OSL_ASSERT(xAxis.is());
if ( xAxis.is() )
{
chart2::ScaleData aScaleData = xAxis->getScaleData();
aTimeIncrement = aScaleData.TimeIncrement;
break ;
}
}
}
}
catch (const uno::Exception&)
{
DBG_UNHANDLED_EXCEPTION("oox" );
}
return aTimeIncrement;
}
static bool lcl_isSeriesAttachedToFirstAxis(
const Reference< chart2::XDataSeries > & xDataSeries )
{
bool bResult=true ;
try
{
sal_Int32 nAxisIndex = 0;
Reference< beans::XPropertySet > xProp( xDataSeries, uno::UNO_QUERY_THROW );
xProp->getPropertyValue(u"AttachedAxisIndex" _ustr) >>= nAxisIndex;
bResult = (0==nAxisIndex);
}
catch ( const uno::Exception & )
{
DBG_UNHANDLED_EXCEPTION("oox" );
}
return bResult;
}
static OUString lcl_flattenStringSequence( const Sequence< OUString > & rSequence )
{
OUStringBuffer aResult;
bool bPrecedeWithSpace = false ;
for ( const auto & rString : rSequence )
{
if ( !rString.isEmpty())
{
if ( bPrecedeWithSpace )
aResult.append( ' ' );
aResult.append( rString );
bPrecedeWithSpace = true ;
}
}
return aResult.makeStringAndClear();
}
static void lcl_writeChartexString(FSHelperPtr pFS, std::u16string_view sOut)
{
pFS->startElement(FSNS(XML_cx, XML_tx));
// cell range doesn't seem to be supported in chartex?
// TODO: also handle <cx:rich>
pFS->startElement(FSNS(XML_cx, XML_txData));
// TODO: also handle <cx:f> <cx:v>
pFS->startElement(FSNS(XML_cx, XML_v));
pFS->writeEscaped(sOut);
pFS->endElement( FSNS( XML_cx, XML_v ) );
pFS->endElement( FSNS( XML_cx, XML_txData ) );
pFS->endElement( FSNS( XML_cx, XML_tx ) );
}
static Sequence< OUString > lcl_getLabelSequence( const Reference< chart2::data::XDataSequence > & xLabelSeq )
{
Sequence< OUString > aLabels;
uno::Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xLabelSeq, uno::UNO_QUERY );
if ( xTextualDataSequence.is())
{
aLabels = xTextualDataSequence->getTextualData();
}
else if ( xLabelSeq.is())
{
const Sequence< uno::Any > aAnies( xLabelSeq->getData());
aLabels.realloc( aAnies.getLength());
auto pLabels = aLabels.getArray();
for ( sal_Int32 i=0; i<aAnies.getLength(); ++i )
aAnies[i] >>= pLabels[i];
}
return aLabels;
}
static void lcl_fillCategoriesIntoStringVector(
const Reference< chart2::data::XDataSequence > & xCategories,
::std::vector< OUString > & rOutCategories )
{
OSL_ASSERT( xCategories.is());
if ( !xCategories.is())
return ;
Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xCategories, uno::UNO_QUERY );
if ( xTextualDataSequence.is())
{
rOutCategories.clear();
const Sequence< OUString > aTextData( xTextualDataSequence->getTextualData());
rOutCategories.insert( rOutCategories.end(), aTextData.begin(), aTextData.end() );
}
else
{
Sequence< uno::Any > aAnies( xCategories->getData());
rOutCategories.resize( aAnies.getLength());
for ( sal_Int32 i=0; i<aAnies.getLength(); ++i )
aAnies[i] >>= rOutCategories[i];
}
}
static ::std::vector< double > lcl_getAllValuesFromSequence( const Reference< chart2::data::XDataSequence > & xSeq )
{
::std::vector< double > aResult;
Reference< chart2::data::XNumericalDataSequence > xNumSeq( xSeq, uno::UNO_QUERY );
if ( xNumSeq.is())
{
const Sequence< double > aValues( xNumSeq->getNumericalData());
aResult.insert( aResult.end(), aValues.begin(), aValues.end() );
}
else if ( xSeq.is())
{
Sequence< uno::Any > aAnies( xSeq->getData());
aResult.resize( aAnies.getLength(), std::numeric_limits<double >::quiet_NaN() );
for ( sal_Int32 i=0; i<aAnies.getLength(); ++i )
aAnies[i] >>= aResult[i];
}
return aResult;
}
namespace
{
constexpr auto constChartTypeMap = frozen::make_unordered_map<std::u16string_view, chart::TypeId >(
{
{ u"com.sun.star.chart.BarDiagram" , chart::TYPEID_BAR },
{ u"com.sun.star.chart2.ColumnChartType" , chart::TYPEID_BAR },
{ u"com.sun.star.chart.AreaDiagram" , chart::TYPEID_AREA },
{ u"com.sun.star.chart2.AreaChartType" , chart::TYPEID_AREA },
{ u"com.sun.star.chart.LineDiagram" , chart::TYPEID_LINE },
{ u"com.sun.star.chart2.LineChartType" , chart::TYPEID_LINE },
{ u"com.sun.star.chart.PieDiagram" , chart::TYPEID_PIE },
{ u"com.sun.star.chart2.PieChartType" , chart::TYPEID_PIE },
{ u"com.sun.star.chart.DonutDiagram" , chart::TYPEID_DOUGHNUT },
{ u"com.sun.star.chart2.DonutChartType" , chart::TYPEID_DOUGHNUT },
{ u"com.sun.star.chart.XYDiagram" , chart::TYPEID_SCATTER },
{ u"com.sun.star.chart2.ScatterChartType" , chart::TYPEID_SCATTER },
{ u"com.sun.star.chart.NetDiagram" , chart::TYPEID_RADARLINE },
{ u"com.sun.star.chart2.NetChartType" , chart::TYPEID_RADARLINE },
{ u"com.sun.star.chart.FilledNetDiagram" , chart::TYPEID_RADARAREA },
{ u"com.sun.star.chart2.FilledNetChartType" , chart::TYPEID_RADARAREA },
{ u"com.sun.star.chart.StockDiagram" , chart::TYPEID_STOCK },
{ u"com.sun.star.chart2.CandleStickChartType" , chart::TYPEID_STOCK },
{ u"com.sun.star.chart.BubbleDiagram" , chart::TYPEID_BUBBLE },
{ u"com.sun.star.chart2.BubbleChartType" , chart::TYPEID_BUBBLE },
{ u"com.sun.star.chart.FunnelDiagram" , chart::TYPEID_FUNNEL },
{ u"com.sun.star.chart2.FunnelChartType" , chart::TYPEID_FUNNEL },
});
} // end anonymous namespace
static sal_Int32 lcl_getChartType(std::u16string_view sChartType)
{
auto aIterator = constChartTypeMap.find(sChartType);
if (aIterator == constChartTypeMap.end())
return chart::TYPEID_UNKNOWN;
return aIterator->second;
}
static sal_Int32 lcl_generateRandomValue()
{
return comphelper::rng::uniform_int_distribution(0, 100000000-1);
}
bool DataLabelsRange::empty() const
{
return maLabels.empty();
}
size_t DataLabelsRange::count() const
{
return maLabels.size();
}
bool DataLabelsRange::hasLabel(sal_Int32 nIndex) const
{
return maLabels.find(nIndex) != maLabels.end();
}
const OUString & DataLabelsRange::getRange() const
{
return maRange;
}
void DataLabelsRange::setRange(const OUString& rRange)
{
maRange = rRange;
}
void DataLabelsRange::setLabel(sal_Int32 nIndex, const OUString& rText)
{
maLabels.emplace(nIndex, rText);
}
DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::begin() const
{
return maLabels.begin();
}
DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::end() const
{
return maLabels.end();
}
ChartExport::ChartExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, Reference< frame::XModel > const & xModel, XmlFilterBase* pFB, DocumentType eDocumentType )
: DrawingML( std::move(pFS), pFB, eDocumentType )
, mnXmlNamespace( nXmlNamespace )
, mnSeriesCount(0)
, mxChartModel( xModel )
, mpURLTransformer(std::make_shared<URLTransformer>())
, mbHasCategoryLabels( false )
, mbHasZAxis( false )
, mbIs3DChart( false )
, mbStacked(false )
, mbPercent(false )
, mbHasDateCategories(false )
{
}
void ChartExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer)
{
mpURLTransformer = pTransformer;
}
sal_Int32 ChartExport::getChartType( )
{
OUString sChartType = mxDiagram->getDiagramType();
return lcl_getChartType( sChartType );
}
namespace {
uno::Sequence< beans::PropertyValue > createArguments(
const OUString & rRangeRepresentation, bool bUseColumns)
{
css::chart::ChartDataRowSource eRowSource = css::chart::ChartDataRowSource_ROWS;
if (bUseColumns)
eRowSource = css::chart::ChartDataRowSource_COLUMNS;
uno::Sequence<beans::PropertyValue> aArguments{
{ u"DataRowSource" _ustr, -1, uno::Any(eRowSource), beans::PropertyState_DIRECT_VALUE },
{ u"FirstCellAsLabel" _ustr, -1, uno::Any(false ), beans::PropertyState_DIRECT_VALUE },
{ u"HasCategories" _ustr, -1, uno::Any(false ), beans::PropertyState_DIRECT_VALUE },
{ u"CellRangeRepresentation" _ustr, -1, uno::Any(rRangeRepresentation),
beans::PropertyState_DIRECT_VALUE }
};
return aArguments;
}
Reference<chart2::XDataSeries> getPrimaryDataSeries(const Reference<chart2::XChartType>& xChartType)
{
Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY_THROW);
// export dataseries for current chart-type
const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
for (const auto & rSeries : aSeriesSeq)
{
Reference<chart2::XDataSeries> xSource(rSeries, uno::UNO_QUERY);
if (xSource.is())
return xSource;
}
return Reference<chart2::XDataSeries>();
}
}
Sequence< Sequence< OUString > > ChartExport::getSplitCategoriesList( const OUString& rRange )
{
Reference< chart2::XChartDocument > xChartDoc(getModel(), uno::UNO_QUERY);
OSL_ASSERT(xChartDoc.is());
if (xChartDoc.is())
{
Reference< chart2::data::XDataProvider > xDataProvider(xChartDoc->getDataProvider());
OSL_ENSURE(xDataProvider.is(), "No DataProvider" );
if (xDataProvider.is())
{
//detect whether the first series is a row or a column
bool bSeriesUsesColumns = true ;
Reference< chart2::XDiagram > xDiagram(xChartDoc->getFirstDiagram());
try
{
Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(xDiagram, uno::UNO_QUERY_THROW);
const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(xCooSysCnt->getCoordinateSystems());
for (const auto & rCooSys : aCooSysSeq)
{
const Reference< chart2::XChartTypeContainer > xCTCnt(rCooSys, uno::UNO_QUERY_THROW);
const Sequence< Reference< chart2::XChartType > > aChartTypeSeq(xCTCnt->getChartTypes());
for (const auto & rChartType : aChartTypeSeq)
{
Reference< chart2::XDataSeries > xDataSeries = getPrimaryDataSeries(rChartType);
if (xDataSeries.is())
{
uno::Reference< chart2::data::XDataSource > xSeriesSource(xDataSeries, uno::UNO_QUERY);
const uno::Sequence< beans::PropertyValue > rArguments = xDataProvider->detectArguments(xSeriesSource);
for (const beans::PropertyValue& rProperty : rArguments)
{
if (rProperty.Name == "DataRowSource" )
{
css::chart::ChartDataRowSource eRowSource;
if (rProperty.Value >>= eRowSource)
{
bSeriesUsesColumns = (eRowSource == css::chart::ChartDataRowSource_COLUMNS);
break ;
}
}
}
}
}
}
}
catch (const uno::Exception &)
{
DBG_UNHANDLED_EXCEPTION("chart2" );
}
// detect we have an inner data table or not
if (xChartDoc->hasInternalDataProvider() && rRange == "categories" )
{
try
{
css::uno::Reference< css::chart2::XAnyDescriptionAccess > xDataAccess(xChartDoc->getDataProvider(), uno::UNO_QUERY);
const Sequence< Sequence< uno::Any > >aAnyCategories(bSeriesUsesColumns ? xDataAccess->getAnyRowDescriptions() : xDataAccess->getAnyColumnDescriptions());
auto pMax = std::max_element(aAnyCategories.begin(), aAnyCategories.end(),
[](const Sequence<uno::Any>& a, const Sequence<uno::Any>& b) {
return a.getLength() < b.getLength(); });
//minimum is 1!
if (pMax != aAnyCategories.end() && pMax->getLength() > 1)
{
sal_Int32 nLevelCount = pMax->getLength();
//we have complex categories
//sort the categories name
Sequence<Sequence<OUString>>aFinalSplitSource(nLevelCount);
auto pFinalSplitSource = aFinalSplitSource.getArray();
for (sal_Int32 i = 0; i < nLevelCount; i++)
{
sal_Int32 nElemLabel = 0;
pFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength());
auto pSeq = pFinalSplitSource[nLevelCount - i - 1].getArray();
for (auto const & elemLabel : aAnyCategories)
{
// make sure elemLabel[i] exists!
if (elemLabel.getLength() > i)
{
pSeq[nElemLabel] = elemLabel[i].get<OUString>();
nElemLabel++;
}
}
}
return aFinalSplitSource;
}
}
catch (const uno::Exception &)
{
DBG_UNHANDLED_EXCEPTION("oox" );
}
}
else
{
try
{
uno::Reference< chart2::data::XDataSource > xCategoriesSource(xDataProvider->createDataSource(
createArguments(rRange, bSeriesUsesColumns)));
if (xCategoriesSource.is())
{
const Sequence< Reference< chart2::data::XLabeledDataSequence >> aCategories = xCategoriesSource->getDataSequences();
if (aCategories.getLength() > 1)
{
//we have complex categories
//sort the categories name
Sequence<Sequence<OUString>> aFinalSplitSource(aCategories.getLength());
std::transform(aCategories.begin(), aCategories.end(),
std::reverse_iterator(asNonConstRange(aFinalSplitSource).end()),
[](const Reference<chart2::data::XLabeledDataSequence>& xCat) {
return lcl_getLabelSequence(xCat->getValues()); });
return aFinalSplitSource;
}
}
}
catch (const uno::Exception &)
{
DBG_UNHANDLED_EXCEPTION("oox" );
}
}
}
}
return Sequence< Sequence< OUString>>(0);
}
OUString ChartExport::parseFormula( const OUString& rRange )
{
OUString aResult;
Reference< XFormulaParser > xParser;
uno::Reference< lang::XMultiServiceFactory > xSF = GetFB()->getModelFactory();
if ( xSF.is() )
{
try
{
xParser.set( xSF->createInstance(u"com.sun.star.sheet.FormulaParser" _ustr), UNO_QUERY );
}
catch ( Exception& )
{
}
}
SAL_WARN_IF(!xParser.is(), "oox" , "creating formula parser failed" );
if ( xParser.is() )
{
Reference< XPropertySet > xParserProps( xParser, uno::UNO_QUERY );
// rRange is the result of a
// css::chart2::data::XDataSequence::getSourceRangeRepresentation()
// call that returns the range in the document's current UI notation.
// Creating a FormulaParser defaults to the same notation, for
// parseFormula() do not attempt to override the FormulaConvention
// property with css::sheet::AddressConvention::OOO or some such.
/* TODO: it would be much better to introduce a
* getSourceRangeRepresentation(css::sheet::AddressConvention) to
* return the ranges in a specific convention than converting them with
* the overhead of creating an XFormulaParser for each... */
uno::Sequence<sheet::FormulaToken> aTokens = xParser->parseFormula( rRange, CellAddress( 0, 0, 0 ) );
if ( xParserProps.is() )
{
xParserProps->setPropertyValue(u"FormulaConvention" _ustr, uno::Any(css::sheet::AddressConvention::XL_OOX) );
// For referencing named ranges correctly with special excel chart syntax.
xParserProps->setPropertyValue(u"RefConventionChartOOXML" _ustr, uno::Any(true ) );
}
aResult = xParser->printFormula( aTokens, CellAddress( 0, 0, 0 ) );
}
else
{
//FIXME: currently just using simple converter, e.g $Sheet1.$A$1:$C$1 -> Sheet1!$A$1:$C$1
OUString aRange( rRange );
if ( aRange.startsWith("$" ) )
aRange = aRange.copy(1);
aRange = aRange.replaceAll(".$" , "!$" );
aResult = aRange;
}
return aResult;
}
void ChartExport::WriteChartObj( const Reference< XShape >& xShape, sal_Int32 nID, sal_Int32 nChartCount )
{
FSHelperPtr pFS = GetFS();
Reference< chart2::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
OSL_ASSERT( xChartDoc.is() );
if ( !xChartDoc.is() )
return ;
// We need to get the new diagram here so we can know if this is a chartex
// chart.
mxNewDiagram.set( xChartDoc->getFirstDiagram());
const bool bIsChartex = isChartexNotChartNS();
if (bIsChartex) {
// Do the AlternateContent header
mpFS->startElementNS(XML_mc, XML_AlternateContent, FSNS(XML_xmlns, XML_mc),
"http://schemas.openxmlformats.org/markup-compatibility/2006 ");
mpFS->startElementNS(XML_mc, XML_Choice,
FSNS(XML_xmlns, XML_cx2), "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex ",
XML_Requires, "cx2" );
}
Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
pFS->startElementNS(mnXmlNamespace, XML_graphicFrame);
pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);
// TODO: get the correct chart name chart id
OUString sName = u"Object 1" _ustr;
Reference< XNamed > xNamed( xShape, UNO_QUERY );
if (xNamed.is())
sName = xNamed->getName();
pFS->startElementNS( mnXmlNamespace, XML_cNvPr,
XML_id, OString::number(nID),
XML_name, sName);
OUString sURL;
if ( GetProperty( xShapeProps, u"URL" _ustr ) )
mAny >>= sURL;
if ( !sURL.isEmpty() )
{
OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
oox::getRelationship(Relationship::HYPERLINK),
mpURLTransformer->getTransformedString(sURL),
mpURLTransformer->isExternalURL(sURL));
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
}
if (bIsChartex) {
pFS->startElement(FSNS(XML_a, XML_extLst));
pFS->startElement(FSNS(XML_a, XML_ext), XML_uri,
"{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}" );
pFS->singleElement(FSNS(XML_a16, XML_creationId),
FSNS(XML_xmlns, XML_a16), "http://schemas.microsoft.com/office/drawing/2014/main ",
XML_id, "{393D7C90-AF84-3958-641C-0FEC03FE8894}" );
pFS->endElement(FSNS(XML_a, XML_ext));
pFS->endElement(FSNS(XML_a, XML_extLst));
}
pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);
if ( GetDocumentType() == DOCUMENT_PPTX )
pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );
// visual chart properties
WriteShapeTransformation( xShape, mnXmlNamespace );
const char *sSchemaURL = bIsChartex?
"http://schemas.microsoft.com/office/drawing/2014/chartex " :
"http://schemas.openxmlformats.org/drawingml/2006/chart ";
// writer chart object
pFS->startElement(FSNS(XML_a, XML_graphic));
pFS->startElement( FSNS( XML_a, XML_graphicData ), XML_uri, sSchemaURL );
OUString sId;
const char * sFullPath = nullptr;
const char * sRelativePath = nullptr;
const char *sChartFnamePrefix = bIsChartex? "chartEx" : "chart" ;
switch ( GetDocumentType() )
{
case DOCUMENT_DOCX:
{
sFullPath = "word/charts/" ;
sRelativePath = "charts/" ;
break ;
}
case DOCUMENT_PPTX:
{
sFullPath = "ppt/charts/" ;
sRelativePath = "../charts/" ;
break ;
}
case DOCUMENT_XLSX:
{
sFullPath = "xl/charts/" ;
sRelativePath = "../charts/" ;
break ;
}
default :
{
sFullPath = "charts/" ;
sRelativePath = "charts/" ;
break ;
}
}
OUString sFullStream = OUStringBuffer()
.appendAscii(sFullPath)
.appendAscii(sChartFnamePrefix)
.append(OUString::number(nChartCount) + ".xml" )
.makeStringAndClear();
OUString sRelativeStream = OUStringBuffer()
.appendAscii(sRelativePath)
.appendAscii(sChartFnamePrefix)
.append(OUString::number(nChartCount) + ".xml" )
.makeStringAndClear();
const OUString sAppURL = bIsChartex?
u"application/vnd.ms-office.chartex+xml" _ustr :
u"application/vnd.openxmlformats-officedocument.drawingml.chart+xml" _ustr;
const Relationship eChartRel = bIsChartex ?
Relationship::CHARTEX :
Relationship::CHART;
FSHelperPtr pChart = CreateOutputStream(
sFullStream,
sRelativeStream,
pFS->getOutputStream(),
sAppURL,
oox::getRelationship(eChartRel),
&sId );
XmlFilterBase* pFB = GetFB();
if (bIsChartex) {
// Use chartex namespace
pFS->singleElement( FSNS( XML_cx, XML_chart ),
FSNS(XML_xmlns, XML_cx), pFB->getNamespaceURL(OOX_NS(cx)),
FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)),
FSNS(XML_r, XML_id), sId );
} else {
pFS->singleElement( FSNS( XML_c, XML_chart ),
FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)),
FSNS(XML_r, XML_id), sId );
}
pFS->endElement( FSNS( XML_a, XML_graphicData ) );
pFS->endElement( FSNS( XML_a, XML_graphic ) );
pFS->endElementNS( mnXmlNamespace, XML_graphicFrame );
if (bIsChartex) {
// Do the AlternateContent fallback path
pFS->endElementNS(XML_mc, XML_Choice);
pFS->startElementNS(XML_mc, XML_Fallback);
pFS->startElementNS(XML_xdr, XML_sp, XML_macro, "" , XML_textlink, "" );
pFS->startElementNS(XML_xdr, XML_nvSpPr);
pFS->singleElementNS(XML_xdr, XML_cNvPr, XML_id, "0" , XML_name, "" );
pFS->startElementNS(XML_xdr, XML_cNvSpPr);
pFS->singleElementNS(XML_a, XML_spLocks, XML_noTextEdit, "1" );
pFS->endElementNS(XML_xdr, XML_cNvSpPr);
pFS->endElementNS(XML_xdr, XML_nvSpPr);
pFS->startElementNS(XML_xdr, XML_spPr);
pFS->startElementNS(XML_a, XML_xfrm);
pFS->singleElementNS(XML_a, XML_off, XML_x, "6600825" , XML_y, "2533650" );
pFS->singleElementNS(XML_a, XML_ext, XML_cx, "4572000" , XML_cy, "2743200" );
pFS->endElementNS(XML_a, XML_xfrm);
pFS->startElementNS(XML_a, XML_prstGeom, XML_prst, "rect" );
pFS->singleElementNS(XML_a, XML_avLst);
pFS->endElementNS(XML_a, XML_prstGeom);
pFS->startElementNS(XML_a, XML_solidFill);
pFS->singleElementNS(XML_a, XML_prstClr, XML_val, "white" );
pFS->endElementNS(XML_a, XML_solidFill);
pFS->startElementNS(XML_a, XML_ln, XML_w, "1" );
pFS->startElementNS(XML_a, XML_solidFill);
pFS->singleElementNS(XML_a, XML_prstClr, XML_val, "green" );
pFS->endElementNS(XML_a, XML_solidFill);
pFS->endElementNS(XML_a, XML_ln);
pFS->endElementNS(XML_xdr, XML_spPr);
pFS->startElementNS(XML_xdr, XML_txBody);
pFS->singleElementNS(XML_a, XML_bodyPr, XML_vertOverflow, "clip" , XML_horzOverflow, "clip" );
pFS->singleElementNS(XML_a, XML_lstStyle);
pFS->startElementNS(XML_a, XML_p);
pFS->startElementNS(XML_a, XML_r);
pFS->singleElementNS(XML_a, XML_rPr, XML_sz, "1100" );
pFS->startElementNS(XML_a, XML_t);
const std::string_view sErrTxt("This chart isn't available in your version of Excel.\n\n"
"Editing this shape or saving this workbook into a different file format will permanently break the chart." );
pFS->writeEscaped( sErrTxt );
pFS->endElementNS(XML_a, XML_t);
pFS->endElementNS(XML_a, XML_r);
pFS->endElementNS(XML_a, XML_p);
pFS->endElementNS(XML_xdr, XML_txBody);
pFS->endElementNS(XML_xdr, XML_sp);
pFS->endElementNS(XML_mc, XML_Fallback);
pFS->endElementNS(XML_mc, XML_AlternateContent);
}
SetFS( pChart );
ExportContent();
if (bIsChartex) {
SetFS( pChart );
sRelativePath ="" ;
FSHelperPtr pChartFS = GetFS();
// output style and colorstyle files
// first style
static constexpr char sStyleFnamePrefix[] = "style" ;
OUStringBuffer sFullStreamBuf;
sFullStreamBuf.appendAscii(sFullPath);
sFullStreamBuf = sFullStreamBuf + sStyleFnamePrefix + OUString::number(nChartCount) + ".xml" ;
sFullStream = sFullStreamBuf.makeStringAndClear();
OUStringBuffer sRelativeStreamBuf;
sRelativeStreamBuf.appendAscii(sRelativePath);
sRelativeStreamBuf = sRelativeStreamBuf + sStyleFnamePrefix + OUString::number(nChartCount) + ".xml" ;
sRelativeStream = sRelativeStreamBuf.makeStringAndClear();
FSHelperPtr pStyle = CreateOutputStream(
sFullStream,
sRelativeStream,
pChartFS->getOutputStream(),
u"application/vnd.ms-office.chartstyle+xml" _ustr,
oox::getRelationship(Relationship::CHARTSTYLE),
&sId,
true /* for some reason this doesn't have a header line */);
SetFS( pStyle );
pFS = GetFS();
pFS->startElement(FSNS(XML_cs, XML_chartStyle),
FSNS( XML_xmlns, XML_cs ), pFB->getNamespaceURL(OOX_NS(cs)),
FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
XML_id, "419" /* no idea what this number is supposed to be */);
outputStyleEntry(pFS, XML_axisTitle);;
outputStyleEntry(pFS, XML_categoryAxis);
outputChartAreaStyleEntry(pFS);
outputStyleEntry(pFS, XML_dataLabel);
outputDataPointStyleEntry(pFS);
outputStyleEntry(pFS, XML_dataPoint3D);
outputStyleEntry(pFS, XML_dataPointLine);
outputStyleEntry(pFS, XML_dataPointMarker);
outputStyleEntry(pFS, XML_dataPointWireframe);
outputStyleEntry(pFS, XML_dataTable);
outputStyleEntry(pFS, XML_downBar);
outputStyleEntry(pFS, XML_dropLine);
outputStyleEntry(pFS, XML_errorBar);
outputStyleEntry(pFS, XML_floor);
outputStyleEntry(pFS, XML_gridlineMajor);
outputStyleEntry(pFS, XML_gridlineMinor);
outputStyleEntry(pFS, XML_hiLoLine);
outputStyleEntry(pFS, XML_leaderLine);
outputStyleEntry(pFS, XML_legend);
outputStyleEntry(pFS, XML_plotArea);
outputStyleEntry(pFS, XML_plotArea3D);
outputStyleEntry(pFS, XML_seriesAxis);
outputStyleEntry(pFS, XML_seriesLine);
outputStyleEntry(pFS, XML_title);
outputStyleEntry(pFS, XML_trendline);
outputStyleEntry(pFS, XML_trendlineLabel);
outputStyleEntry(pFS, XML_upBar);
outputStyleEntry(pFS, XML_valueAxis);
outputStyleEntry(pFS, XML_wall);
pFS->endElement(FSNS(XML_cs, XML_chartStyle));
pStyle->endDocument();
// now colorstyle
static constexpr char sColorFnamePrefix[] = "colors" ;
sFullStreamBuf = OUStringBuffer();
sFullStreamBuf.appendAscii(sFullPath);
sFullStreamBuf = sFullStreamBuf + sColorFnamePrefix + OUString::number(nChartCount) + ".xml" ;
sFullStream = sFullStreamBuf.makeStringAndClear();
sRelativeStreamBuf = OUStringBuffer();
sRelativeStreamBuf.appendAscii(sRelativePath);
sRelativeStreamBuf = sRelativeStreamBuf + sColorFnamePrefix + OUString::number(nChartCount) + ".xml" ;
sRelativeStream = sRelativeStreamBuf.makeStringAndClear();
FSHelperPtr pColorStyle = CreateOutputStream(
sFullStream,
sRelativeStream,
pChartFS->getOutputStream(),
u"application/vnd.ms-office.chartcolorstyle+xml" _ustr,
oox::getRelationship(Relationship::CHARTCOLORSTYLE),
&sId,
true /* also no header line */);
SetFS( pColorStyle );
pFS = GetFS();
pFS->startElement(FSNS(XML_cs, XML_colorStyle),
FSNS( XML_xmlns, XML_cs ), pFB->getNamespaceURL(OOX_NS(cs)),
FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
XML_meth, "cycle" ,
XML_id, "10" /* no idea what this number is supposed to be */);
pFS->singleElement(FSNS(XML_a, XML_schemeClr),
XML_val, "accent1" );
pFS->endElement(FSNS(XML_cs, XML_colorStyle));
pColorStyle->endDocument();
}
pChart->endDocument();
}
void ChartExport::InitRangeSegmentationProperties( const Reference< chart2::XChartDocument > & xChartDoc )
{
if ( !xChartDoc.is())
return ;
try
{
Reference< chart2::data::XDataProvider > xDataProvider( xChartDoc->getDataProvider() );
OSL_ENSURE( xDataProvider.is(), "No DataProvider" );
if ( xDataProvider.is())
{
mbHasCategoryLabels = lcl_hasCategoryLabels( xChartDoc );
}
}
catch ( const uno::Exception & )
{
DBG_UNHANDLED_EXCEPTION("oox" );
}
}
void ChartExport::ExportContent()
{
Reference< chart2::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
OSL_ASSERT( xChartDoc.is() );
if ( !xChartDoc.is() )
return ;
InitRangeSegmentationProperties( xChartDoc );
const bool bIsChartex = isChartexNotChartNS();
ExportContent_( bIsChartex );
}
void ChartExport::ExportContent_( bool bIsChartex )
{
Reference< css::chart::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
if ( xChartDoc.is())
{
// determine if data comes from the outside
bool bIncludeTable = true ;
Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
if ( xNewDoc.is())
{
// check if we have own data. If so we must not export the complete
// range string, as this is our only indicator for having own or
// external data. @todo: fix this in the file format!
Reference< lang::XServiceInfo > xDPServiceInfo( xNewDoc->getDataProvider(), uno::UNO_QUERY );
if ( ! (xDPServiceInfo.is() && xDPServiceInfo->getImplementationName() == "com.sun.star.comp.chart.InternalDataProvider" ))
{
bIncludeTable = false ;
}
}
exportChartSpace( xChartDoc, bIncludeTable, bIsChartex );
}
else
{
OSL_FAIL( "Couldn't export chart due to wrong XModel" );
}
}
void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument >& xChartDoc,
bool bIncludeTable,
bool bIsChartex)
{
FSHelperPtr pFS = GetFS();
XmlFilterBase* pFB = GetFB();
const sal_Int32 nChartNS = bIsChartex ? XML_cx : XML_c;
if (bIsChartex) {
pFS->startElement( FSNS( nChartNS, XML_chartSpace ),
FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel)),
FSNS( XML_xmlns, XML_cx ), pFB->getNamespaceURL(OOX_NS(cx)));
} else {
pFS->startElement( FSNS( nChartNS, XML_chartSpace ),
FSNS( XML_xmlns, XML_c ), pFB->getNamespaceURL(OOX_NS(dmlChart)),
FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel)));
}
if ( !bIncludeTable )
{
// TODO:external data
}
else
{
Reference< XPropertySet > xPropSet(xChartDoc, UNO_QUERY);
Any aNullDate = xPropSet->getPropertyValue("NullDate" );
util::DateTime aDate;
if ((aNullDate >>= aDate) && (aDate.Year == 1904 && aDate.Month == 1 && aDate.Day == 1))
{
pFS->singleElement(FSNS(XML_c, XML_date1904), XML_val, "1" );
}
else
{
pFS->singleElement(FSNS(XML_c, XML_date1904), XML_val, "0" );
}
}
// TODO: get the correct editing language
if (bIsChartex) {
// chartData
pFS->startElement(FSNS(XML_cx, XML_chartData));
exportExternalData(xChartDoc, true );
exportData(xChartDoc, true );
pFS->endElement(FSNS(XML_cx, XML_chartData));
} else {
pFS->singleElement(FSNS(XML_c, XML_lang), XML_val, "en-US" );
pFS->singleElement(FSNS(XML_c, XML_roundedCorners), XML_val, "0" );
}
// style
if (!bIsChartex) {
mxDiagram.set( xChartDoc->getDiagram() );
Reference< XPropertySet > xPropSet(mxDiagram, uno::UNO_QUERY);
if (GetProperty(xPropSet, u"StyleIndex" _ustr)) {
sal_Int32 nStyleIdx = -1;
mAny >>= nStyleIdx;
assert(nStyleIdx >= 0);
pFS->singleElement(FSNS(XML_c, XML_style), XML_val,
OUString::number(nStyleIdx));
}
}
//XML_chart
exportChart(xChartDoc, bIsChartex);
// TODO: printSettings
// TODO: text properties
Reference< XPropertySet > xPropSet = xChartDoc->getArea();
if ( xPropSet.is() )
exportShapeProps( xPropSet, bIsChartex );
// TODO for chartex
if (!bIsChartex) {
//XML_externalData
exportExternalData(xChartDoc, false );
}
// export additional shapes in chart
if (!bIsChartex) {
exportAdditionalShapes(xChartDoc);
}
pFS->endElement( FSNS( nChartNS, XML_chartSpace ) );
}
void ChartExport::exportData( [[maybe_unused]] const Reference< css::chart::XChartDocument >& xChartDoc,
bool bIsChartex)
{
if (bIsChartex) {
FSHelperPtr pFS = GetFS();
// Not sure if the data id is always 0. However, it seems it may need to
// agree with the id in exportSeries(). See DATA_ID_COMMENT
pFS->startElement(FSNS(XML_cx, XML_data), XML_id, "0" );
// Just hard-coding this for now
pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, "val" );
pFS->startElement(FSNS(XML_cx, XML_f));
pFS->writeEscaped("_xlchart.v2.0" ); // I have no idea what this
// means or what it should be in
// general
pFS->endElement(FSNS(XML_cx, XML_f));
pFS->endElement(FSNS(XML_cx, XML_numDim));
pFS->endElement(FSNS(XML_cx, XML_data));
}
}
void ChartExport::exportExternalData( const Reference< css::chart::XChartDocument >& xChartDoc,
bool bIsChartex)
{
if (bIsChartex) return ; // TODO!!
// Embedded external data is grab bagged for docx file hence adding export part of
// external data for docx files only.
if (GetDocumentType() != DOCUMENT_DOCX)
return ;
OUString externalDataPath;
Reference< beans::XPropertySet > xDocPropSet( xChartDoc->getDiagram(), uno::UNO_QUERY );
if ( xDocPropSet.is())
{
try
{
Any aAny( xDocPropSet->getPropertyValue( u"ExternalData" _ustr ));
aAny >>= externalDataPath;
}
catch ( beans::UnknownPropertyException & )
{
SAL_WARN("oox" , "Required property not found in ChartDocument" );
}
}
if (externalDataPath.isEmpty())
return ;
// Here adding external data entry to relationship.
OUString relationPath = externalDataPath;
// Converting absolute path to relative path.
if ( externalDataPath[ 0 ] != '.' && externalDataPath[ 1 ] != '.' )
{
sal_Int32 nSepPos = externalDataPath.indexOf( '/' , 0 );
if ( nSepPos > 0)
{
relationPath = relationPath.copy( nSepPos, ::std::max< sal_Int32 >( externalDataPath.getLength(), 0 ) - nSepPos );
relationPath = ".." + relationPath;
}
}
FSHelperPtr pFS = GetFS();
OUString type = oox::getRelationship(Relationship::PACKAGE);
if (relationPath.endsWith(".bin" ))
type = oox::getRelationship(Relationship::OLEOBJECT);
OUString sRelId = GetFB()->addRelation(pFS->getOutputStream(),
type,
relationPath);
pFS->singleElementNS(XML_c, XML_externalData, FSNS(XML_r, XML_id), sRelId);
}
void ChartExport::exportAdditionalShapes( const Reference< css::chart::XChartDocument >& xChartDoc )
{
// Not used in chartex
Reference< beans::XPropertySet > xDocPropSet(xChartDoc, uno::UNO_QUERY);
if (!xDocPropSet.is())
return ;
css::uno::Reference< css::drawing::XShapes > mxAdditionalShapes;
// get a sequence of non-chart shapes
try
{
Any aShapesAny = xDocPropSet->getPropertyValue(u"AdditionalShapes" _ustr);
if ( (aShapesAny >>= mxAdditionalShapes) && mxAdditionalShapes.is() )
{
OUString sId;
const char * sFullPath = nullptr;
const char * sRelativePath = nullptr;
sal_Int32 nDrawing = getNewDrawingUniqueId();
switch (GetDocumentType())
{
case DOCUMENT_DOCX:
{
sFullPath = "word/drawings/drawing" ;
sRelativePath = "../drawings/drawing" ;
break ;
}
case DOCUMENT_PPTX:
{
sFullPath = "ppt/drawings/drawing" ;
sRelativePath = "../drawings/drawing" ;
break ;
}
case DOCUMENT_XLSX:
{
sFullPath = "xl/drawings/drawing" ;
sRelativePath = "../drawings/drawing" ;
break ;
}
default :
{
sFullPath = "drawings/drawing" ;
sRelativePath = "drawings/drawing" ;
break ;
}
}
OUString sFullStream = OUStringBuffer()
.appendAscii(sFullPath)
.append(OUString::number(nDrawing) + ".xml" )
.makeStringAndClear();
OUString sRelativeStream = OUStringBuffer()
.appendAscii(sRelativePath)
.append(OUString::number(nDrawing) + ".xml" )
.makeStringAndClear();
sax_fastparser::FSHelperPtr pDrawing = CreateOutputStream(
sFullStream,
sRelativeStream,
GetFS()->getOutputStream(),
u"application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml" _ustr,
oox::getRelationship(Relationship::CHARTUSERSHAPES),
&sId);
GetFS()->singleElementNS(XML_c, XML_userShapes, FSNS(XML_r, XML_id), sId);
XmlFilterBase* pFB = GetFB();
pDrawing->startElement(FSNS(XML_c, XML_userShapes),
FSNS(XML_xmlns, XML_cdr), pFB->getNamespaceURL(OOX_NS(dmlChartDr)),
FSNS(XML_xmlns, XML_a), pFB->getNamespaceURL(OOX_NS(dml)),
FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)));
const sal_Int32 nShapeCount(mxAdditionalShapes->getCount());
for (sal_Int32 nShapeId = 0; nShapeId < nShapeCount; nShapeId++)
{
Reference< drawing::XShape > xShape;
mxAdditionalShapes->getByIndex(nShapeId) >>= xShape;
SAL_WARN_IF(!xShape.is(), "xmloff.chart" , "Shape without an XShape?" );
if (!xShape.is())
continue ;
// TODO: absSizeAnchor: we import both (absSizeAnchor and relSizeAnchor), but there is no essential difference between them.
pDrawing->startElement(FSNS(XML_cdr, XML_relSizeAnchor));
uno::Reference< beans::XPropertySet > xShapeProperties(xShape, uno::UNO_QUERY);
if ( xShapeProperties.is() )
{
Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
WriteFromTo( xShape, aPageSize, pDrawing );
ShapeExport aExport(XML_cdr, pDrawing, nullptr, GetFB(), GetDocumentType(), nullptr, true );
aExport.WriteShape(xShape);
}
pDrawing->endElement(FSNS(XML_cdr, XML_relSizeAnchor));
}
pDrawing->endElement(FSNS(XML_c, XML_userShapes));
pDrawing->endDocument();
}
}
catch (const uno::Exception&)
{
TOOLS_INFO_EXCEPTION("xmloff.chart" , "AdditionalShapes not found" );
}
}
void ChartExport::exportChart( const Reference< css::chart::XChartDocument >& xChartDoc,
bool bIsChartex)
{
Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
mxDiagram.set( xChartDoc->getDiagram() );
if ( xNewDoc.is()) {
mxNewDiagram.set( xNewDoc->getFirstDiagram());
}
// get Properties of ChartDocument
bool bHasMainTitle = false ;
bool bHasLegend = false ;
Reference< beans::XPropertySet > xDocPropSet( xChartDoc, uno::UNO_QUERY );
if ( xDocPropSet.is())
{
try
{
Any aAny( xDocPropSet->getPropertyValue(u"HasMainTitle" _ustr));
aAny >>= bHasMainTitle;
aAny = xDocPropSet->getPropertyValue(u"HasLegend" _ustr);
aAny >>= bHasLegend;
}
catch ( beans::UnknownPropertyException & )
{
SAL_WARN("oox" , "Required property not found in ChartDocument" );
}
} // if( xDocPropSet.is())
Sequence< uno::Reference< chart2::XFormattedString > > xFormattedSubTitle;
Reference< beans::XPropertySet > xPropSubTitle( xChartDoc->getSubTitle(), UNO_QUERY );
if ( xPropSubTitle.is())
{
OUString aSubTitle;
if ((xPropSubTitle->getPropertyValue(u"String" _ustr) >>= aSubTitle) && !aSubTitle.isEmpty())
xPropSubTitle->getPropertyValue(u"FormattedStrings" _ustr) >>= xFormattedSubTitle;
}
// chart element
FSHelperPtr pFS = GetFS();
const sal_Int32 nChartNS = bIsChartex ? XML_cx : XML_c;
pFS->startElement(FSNS(nChartNS, XML_chart));
// titles
if ( bHasMainTitle )
{
exportTitle( xChartDoc->getTitle(), bIsChartex, xFormattedSubTitle);
if (!bIsChartex) {
pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0" );
}
}
else if ( xFormattedSubTitle.hasElements() )
{
exportTitle( xChartDoc->getSubTitle(), bIsChartex );
if (!bIsChartex) {
pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0" );
}
}
else if (!bIsChartex) {
pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "1" );
}
InitPlotArea( );
if ( mbIs3DChart )
{
if (!bIsChartex) {
exportView3D();
// floor
Reference< beans::XPropertySet > xFloor = mxNewDiagram->getFloor();
if ( xFloor.is() )
{
pFS->startElement(FSNS(XML_c, XML_floor));
exportShapeProps( xFloor, false );
pFS->endElement( FSNS( XML_c, XML_floor ) );
}
// LibreOffice doesn't distinguish between sideWall and backWall (both are using the same color).
// It is controlled by the same Wall property.
Reference< beans::XPropertySet > xWall = mxNewDiagram->getWall();
if ( xWall.is() )
{
// sideWall
pFS->startElement(FSNS(XML_c, XML_sideWall));
exportShapeProps( xWall, false );
pFS->endElement( FSNS( XML_c, XML_sideWall ) );
// backWall
pFS->startElement(FSNS(XML_c, XML_backWall));
exportShapeProps( xWall, false );
pFS->endElement( FSNS( XML_c, XML_backWall ) );
}
}
}
// plot area
exportPlotArea( xChartDoc, bIsChartex );
// legend
if ( bHasLegend ) {
exportLegend( xChartDoc, bIsChartex );
}
if (!bIsChartex) {
uno::Reference<beans::XPropertySet> xDiagramPropSet(xChartDoc->getDiagram(), uno::UNO_QUERY);
uno::Any aPlotVisOnly = xDiagramPropSet->getPropertyValue(u"IncludeHiddenCells" _ustr);
bool bIncludeHiddenCells = false ;
aPlotVisOnly >>= bIncludeHiddenCells;
pFS->singleElement(FSNS(XML_c, XML_plotVisOnly), XML_val, ToPsz10(!bIncludeHiddenCells));
exportMissingValueTreatment(Reference<beans::XPropertySet>(mxDiagram, uno::UNO_QUERY));
}
pFS->endElement( FSNS( nChartNS, XML_chart ) );
}
void ChartExport::exportMissingValueTreatment(const uno::Reference<beans::XPropertySet>& xPropSet)
{
if (!xPropSet.is())
return ;
sal_Int32 nVal = 0;
uno::Any aAny = xPropSet->getPropertyValue(u"MissingValueTreatment" _ustr);
if (!(aAny >>= nVal))
return ;
const char * pVal = nullptr;
switch (nVal)
{
case cssc::MissingValueTreatment::LEAVE_GAP:
pVal = "gap" ;
break ;
case cssc::MissingValueTreatment::USE_ZERO:
pVal = "zero" ;
break ;
case cssc::MissingValueTreatment::CONTINUE :
pVal = "span" ;
break ;
default :
SAL_WARN("oox" , "unknown MissingValueTreatment value" );
break ;
}
FSHelperPtr pFS = GetFS();
pFS->singleElement(FSNS(XML_c, XML_dispBlanksAs), XML_val, pVal);
}
void ChartExport::exportLegend( const Reference< css::chart::XChartDocument >& xChartDoc,
bool bIsChartex)
{
FSHelperPtr pFS = GetFS();
Reference< beans::XPropertySet > xProp( xChartDoc->getLegend(), uno::UNO_QUERY );
if ( xProp.is() )
{
if (!bIsChartex) {
pFS->startElement(FSNS(XML_c, XML_legend));
}
// position
css::chart::ChartLegendPosition aLegendPos = css::chart::ChartLegendPosition_NONE;
try
{
Any aAny( xProp->getPropertyValue( u"Alignment" _ustr ));
aAny >>= aLegendPos;
}
catch ( beans::UnknownPropertyException & )
{
SAL_WARN("oox" , "Property Align not found in ChartLegend" );
}
const char * strPos = nullptr;
switch ( aLegendPos )
{
case css::chart::ChartLegendPosition_LEFT:
strPos = "l" ;
break ;
case css::chart::ChartLegendPosition_RIGHT:
strPos = "r" ;
break ;
case css::chart::ChartLegendPosition_TOP:
strPos = "t" ;
break ;
case css::chart::ChartLegendPosition_BOTTOM:
strPos = "b" ;
break ;
case css::chart::ChartLegendPosition_NONE:
case css::chart::ChartLegendPosition::ChartLegendPosition_MAKE_FIXED_SIZE:
// nothing
break ;
}
if (!bIsChartex) {
if ( strPos != nullptr )
{
pFS->singleElement(FSNS(XML_c, XML_legendPos), XML_val, strPos);
}
// legendEntry
Reference<chart2::XCoordinateSystemContainer> xCooSysContainer(mxNewDiagram, UNO_QUERY_THROW);
const Sequence<Reference<chart2::XCoordinateSystem>> xCooSysSequence(xCooSysContainer->getCoordinateSystems());
sal_Int32 nIndex = 0;
bool bShowLegendEntry;
for (const auto & rCooSys : xCooSysSequence)
{
PropertySet aCooSysProp(rCooSys);
bool bSwapXAndY = aCooSysProp.getBoolProperty(PROP_SwapXAndYAxis);
Reference<chart2::XChartTypeContainer> xChartTypeContainer(rCooSys, UNO_QUERY_THROW);
const Sequence<Reference<chart2::XChartType>> xChartTypeSequence(xChartTypeContainer->getChartTypes());
if (!xChartTypeSequence.hasElements())
continue ;
for (const auto & rCT : xChartTypeSequence)
{
Reference<chart2::XDataSeriesContainer> xDSCont(rCT, UNO_QUERY);
if (!xDSCont.is())
continue ;
OUString aChartType(rCT->getChartType());
bool bIsPie = lcl_getChartType(aChartType) == chart::TYPEID_PIE;
if (bIsPie)
{
PropertySet xChartTypeProp(rCT);
bIsPie = !xChartTypeProp.getBoolProperty(PROP_UseRings);
}
const Sequence<Reference<chart2::XDataSeries>> aDataSeriesSeq = xDSCont->getDataSeries();
if (bSwapXAndY)
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=96 H=97 G=96
¤ Dauer der Verarbeitung: 0.18 Sekunden
¤
*© Formatika GbR, Deutschland