Files
loongoffice/chart2/source/tools/ExplicitCategoriesProvider.cxx
Balazs Varga 2d51b9fd4e tdf#136267 OOXML Chart Import: create main category axis labels once
because InternalDataProvider can not handle different category names
on the primary and secondary category axis.

Revert e0b0502516a10181bbd1737b93b38b2bba4c98e8 commit, except
the relevant unit test.

Regression from commit: e0b0502516a10181bbd1737b93b38b2bba4c98e8
(tdf#128016 Chart OOXML Import: fix duplicated category labels)

Also fix tdf#129994 (FILEOPEN - hang at import time), which is a
a regression from commit fa0a981af41a2606541eec1cb20a379a739691e0
(tdf#114166 DOCX chart import: fix missing complex categories)

Change-Id: I5d049e760eb1a647ea774be264349a2f16f15f5b
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/102463
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
2020-09-18 13:24:46 +02:00

562 lines
20 KiB
C++

/* -*- 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 <ExplicitCategoriesProvider.hxx>
#include <DiagramHelper.hxx>
#include <ChartTypeHelper.hxx>
#include <AxisHelper.hxx>
#include <DataSourceHelper.hxx>
#include <ChartModel.hxx>
#include <ChartModelHelper.hxx>
#include <NumberFormatterWrapper.hxx>
#include <unonames.hxx>
#include <com/sun/star/chart2/AxisType.hpp>
#include <o3tl/safeint.hxx>
#include <rtl/math.hxx>
#include <tools/diagnose_ex.h>
namespace chart
{
using namespace ::com::sun::star;
using namespace ::com::sun::star::chart2;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Sequence;
using std::vector;
ExplicitCategoriesProvider::ExplicitCategoriesProvider( const Reference< chart2::XCoordinateSystem >& xCooSysModel
, ChartModel& rModel )
: m_bDirty(true)
, m_xCooSysModel( xCooSysModel )
, mrModel(rModel)
, m_xOriginalCategories()
, m_bIsExplicitCategoriesInited(false)
, m_bIsDateAxis(false)
, m_bIsAutoDate(false)
{
try
{
if( xCooSysModel.is() )
{
// TODO: handle different category names on the primary and secondary category axis.
uno::Reference< XAxis > xAxis( xCooSysModel->getAxisByDimension(0,0) );
if( xAxis.is() )
{
ScaleData aScale( xAxis->getScaleData() );
m_xOriginalCategories = aScale.Categories;
m_bIsAutoDate = (aScale.AutoDateAxis && aScale.AxisType==chart2::AxisType::CATEGORY);
m_bIsDateAxis = (aScale.AxisType == chart2::AxisType::DATE || m_bIsAutoDate);
}
}
if( m_xOriginalCategories.is() )
{
uno::Reference< data::XDataProvider > xDataProvider( mrModel.getDataProvider() );
OUString aCategoriesRange( DataSourceHelper::getRangeFromValues( m_xOriginalCategories ) );
if( xDataProvider.is() && !aCategoriesRange.isEmpty() )
{
const bool bFirstCellAsLabel = false;
const bool bHasCategories = false;
const uno::Sequence< sal_Int32 > aSequenceMapping;
uno::Reference< data::XDataSource > xColumnCategoriesSource( xDataProvider->createDataSource(
DataSourceHelper::createArguments( aCategoriesRange, aSequenceMapping, true /*bUseColumns*/
, bFirstCellAsLabel, bHasCategories ) ) );
uno::Reference< data::XDataSource > xRowCategoriesSource( xDataProvider->createDataSource(
DataSourceHelper::createArguments( aCategoriesRange, aSequenceMapping, false /*bUseColumns*/
, bFirstCellAsLabel, bHasCategories ) ) );
if( xColumnCategoriesSource.is() && xRowCategoriesSource.is() )
{
Sequence< Reference< data::XLabeledDataSequence> > aColumns = xColumnCategoriesSource->getDataSequences();
Sequence< Reference< data::XLabeledDataSequence> > aRows = xRowCategoriesSource->getDataSequences();
sal_Int32 nColumnCount = aColumns.getLength();
sal_Int32 nRowCount = aRows.getLength();
if( nColumnCount>1 && nRowCount>1 )
{
//we have complex categories
//->split them in the direction of the first series
//detect whether the first series is a row or a column
bool bSeriesUsesColumns = true;
std::vector< Reference< XDataSeries > > aSeries( ChartModelHelper::getDataSeries( mrModel ) );
if( !aSeries.empty() )
{
uno::Reference< data::XDataSource > xSeriesSource( aSeries.front(), uno::UNO_QUERY );
OUString aStringDummy;
bool bDummy;
uno::Sequence< sal_Int32 > aSeqDummy;
DataSourceHelper::readArguments( xDataProvider->detectArguments( xSeriesSource),
aStringDummy, aSeqDummy, bSeriesUsesColumns, bDummy, bDummy );
}
if( bSeriesUsesColumns )
m_aSplitCategoriesList=aColumns;
else
m_aSplitCategoriesList=aRows;
}
}
}
if( !m_aSplitCategoriesList.hasElements() )
{
m_aSplitCategoriesList.realloc(1);
m_aSplitCategoriesList[0]=m_xOriginalCategories;
}
}
}
catch( const uno::Exception & )
{
DBG_UNHANDLED_EXCEPTION("chart2");
}
}
ExplicitCategoriesProvider::~ExplicitCategoriesProvider()
{
}
Reference< chart2::data::XDataSequence > ExplicitCategoriesProvider::getOriginalCategories()
{
if( m_xOriginalCategories.is() )
return m_xOriginalCategories->getValues();
return nullptr;
}
bool ExplicitCategoriesProvider::hasComplexCategories() const
{
return m_aSplitCategoriesList.getLength() > 1;
}
sal_Int32 ExplicitCategoriesProvider::getCategoryLevelCount() const
{
sal_Int32 nCount = m_aSplitCategoriesList.getLength();
if(!nCount)
nCount = 1;
return nCount;
}
static std::vector<sal_Int32> lcl_getLimitingBorders( const std::vector< ComplexCategory >& rComplexCategories )
{
std::vector<sal_Int32> aLimitingBorders;
sal_Int32 nBorderIndex = 0; /*border below the index*/
for (auto const& complexCategory : rComplexCategories)
{
nBorderIndex += complexCategory.Count;
aLimitingBorders.push_back(nBorderIndex);
}
return aLimitingBorders;
}
void ExplicitCategoriesProvider::convertCategoryAnysToText( uno::Sequence< OUString >& rOutTexts, const uno::Sequence< uno::Any >& rInAnys, ChartModel& rModel )
{
sal_Int32 nCount = rInAnys.getLength();
if(!nCount)
return;
rOutTexts.realloc(nCount);
sal_Int32 nAxisNumberFormat = 0;
Reference< XCoordinateSystem > xCooSysModel( ChartModelHelper::getFirstCoordinateSystem( rModel ) );
if( xCooSysModel.is() )
{
Reference< chart2::XAxis > xAxis( xCooSysModel->getAxisByDimension(0,0) );
nAxisNumberFormat = AxisHelper::getExplicitNumberFormatKeyForAxis(
xAxis, xCooSysModel, uno::Reference<chart2::XChartDocument>(static_cast< ::cppu::OWeakObject* >(&rModel), uno::UNO_QUERY), false );
}
Color nLabelColor;
bool bColorChanged = false;
NumberFormatterWrapper aNumberFormatterWrapper( rModel.getNumberFormatsSupplier() );
for(sal_Int32 nN=0;nN<nCount;nN++)
{
OUString aText;
uno::Any aAny = rInAnys[nN];
if( aAny.hasValue() )
{
double fDouble = 0;
if( aAny>>=fDouble )
{
if( !std::isnan(fDouble) )
aText = aNumberFormatterWrapper.getFormattedString(
nAxisNumberFormat, fDouble, nLabelColor, bColorChanged );
}
else
{
aAny>>=aText;
}
}
rOutTexts[nN] = aText;
}
}
SplitCategoriesProvider::~SplitCategoriesProvider()
{
}
namespace {
class SplitCategoriesProvider_ForLabeledDataSequences : public SplitCategoriesProvider
{
public:
explicit SplitCategoriesProvider_ForLabeledDataSequences(
const css::uno::Sequence<
css::uno::Reference< css::chart2::data::XLabeledDataSequence> >& rSplitCategoriesList
, ChartModel& rModel )
: m_rSplitCategoriesList( rSplitCategoriesList )
, mrModel( rModel )
{}
virtual sal_Int32 getLevelCount() const override;
virtual uno::Sequence< OUString > getStringsForLevel( sal_Int32 nIndex ) const override;
private:
const css::uno::Sequence< css::uno::Reference<
css::chart2::data::XLabeledDataSequence> >& m_rSplitCategoriesList;
ChartModel& mrModel;
};
}
sal_Int32 SplitCategoriesProvider_ForLabeledDataSequences::getLevelCount() const
{
return m_rSplitCategoriesList.getLength();
}
uno::Sequence< OUString > SplitCategoriesProvider_ForLabeledDataSequences::getStringsForLevel( sal_Int32 nLevel ) const
{
uno::Sequence< OUString > aRet;
Reference< data::XLabeledDataSequence > xLabeledDataSequence( m_rSplitCategoriesList[nLevel] );
if( xLabeledDataSequence.is() )
{
uno::Reference< data::XDataSequence > xDataSequence( xLabeledDataSequence->getValues() );
if( xDataSequence.is() )
ExplicitCategoriesProvider::convertCategoryAnysToText( aRet, xDataSequence->getData(), mrModel );
}
return aRet;
}
static std::vector< ComplexCategory > lcl_DataSequenceToComplexCategoryVector(
const uno::Sequence< OUString >& rStrings
, const std::vector<sal_Int32>& rLimitingBorders, bool bCreateSingleCategories )
{
std::vector< ComplexCategory > aResult;
sal_Int32 nMaxCount = rStrings.getLength();
OUString aPrevious;
sal_Int32 nCurrentCount=0;
for( sal_Int32 nN=0; nN<nMaxCount; nN++ )
{
const OUString& aCurrent = rStrings[nN];
if( bCreateSingleCategories || std::find( rLimitingBorders.begin(), rLimitingBorders.end(), nN ) != rLimitingBorders.end() )
{
aResult.emplace_back(aPrevious,nCurrentCount );
nCurrentCount=1;
aPrevious = aCurrent;
}
else
{
// Empty value is interpreted as a continuation of the previous
// category. Note that having the same value as the previous one
// does not equate to a continuation of the category.
if (aCurrent.isEmpty())
++nCurrentCount;
else
{
aResult.emplace_back(aPrevious,nCurrentCount );
nCurrentCount=1;
aPrevious = aCurrent;
}
}
}
if( nCurrentCount )
aResult.emplace_back(aPrevious,nCurrentCount );
return aResult;
}
static sal_Int32 lcl_getCategoryCount( std::vector< ComplexCategory >& rComplexCategories )
{
sal_Int32 nCount = 0;
for (auto const& complexCategory : rComplexCategories)
nCount+=complexCategory.Count;
return nCount;
}
static Sequence< OUString > lcl_getExplicitSimpleCategories(
const SplitCategoriesProvider& rSplitCategoriesProvider,
std::vector< std::vector< ComplexCategory > >& rComplexCats )
{
Sequence< OUString > aRet;
rComplexCats.clear();
sal_Int32 nLCount = rSplitCategoriesProvider.getLevelCount();
for( sal_Int32 nL = 0; nL < nLCount; nL++ )
{
std::vector<sal_Int32> aLimitingBorders;
if(nL>0)
aLimitingBorders = lcl_getLimitingBorders( rComplexCats.back() );
rComplexCats.push_back( lcl_DataSequenceToComplexCategoryVector(
rSplitCategoriesProvider.getStringsForLevel(nL), aLimitingBorders, nL==(nLCount-1) ) );
}
//ensure that the category count is the same on each level
sal_Int32 nMaxCategoryCount = 0;
{
for (auto & complexCat : rComplexCats)
{
sal_Int32 nCurrentCount = lcl_getCategoryCount(complexCat);
nMaxCategoryCount = std::max( nCurrentCount, nMaxCategoryCount );
}
for (auto & complexCat : rComplexCats)
{
if ( !complexCat.empty() )
{
sal_Int32 nCurrentCount = lcl_getCategoryCount(complexCat);
if( nCurrentCount< nMaxCategoryCount )
{
ComplexCategory& rComplexCategory = complexCat.back();
rComplexCategory.Count += (nMaxCategoryCount-nCurrentCount);
}
}
}
}
//create a list with an element for every index
std::vector< std::vector< ComplexCategory > > aComplexCatsPerIndex;
for (auto const& complexCat : rComplexCats)
{
std::vector< ComplexCategory > aSingleLevel;
for (auto const& elem : complexCat)
{
sal_Int32 nCount = elem.Count;
while( nCount-- )
aSingleLevel.push_back(elem);
}
aComplexCatsPerIndex.push_back( aSingleLevel );
}
if(nMaxCategoryCount)
{
aRet.realloc(nMaxCategoryCount);
for(sal_Int32 nN=0; nN<nMaxCategoryCount; nN++)
{
OUStringBuffer aText;
for (auto const& complexCatPerIndex : aComplexCatsPerIndex)
{
if ( o3tl::make_unsigned(nN) < complexCatPerIndex.size() )
{
OUString aAddText = complexCatPerIndex[nN].Text;
if( !aAddText.isEmpty() )
{
if(!aText.isEmpty())
aText.append(" ");
aText.append(aAddText);
}
}
}
aRet[nN]=aText.makeStringAndClear();
}
}
return aRet;
}
Sequence< OUString > ExplicitCategoriesProvider::getExplicitSimpleCategories(
const SplitCategoriesProvider& rSplitCategoriesProvider )
{
vector< vector< ComplexCategory > > aComplexCats;
return lcl_getExplicitSimpleCategories( rSplitCategoriesProvider, aComplexCats );
}
static bool lcl_fillDateCategories( const uno::Reference< data::XDataSequence >& xDataSequence, std::vector< double >& rDateCategories, bool bIsAutoDate, ChartModel& rModel )
{
bool bOnlyDatesFound = true;
bool bAnyDataFound = false;
if( xDataSequence.is() )
{
uno::Sequence< uno::Any > aValues = xDataSequence->getData();
sal_Int32 nCount = aValues.getLength();
rDateCategories.reserve(nCount);
Reference< util::XNumberFormats > xNumberFormats( rModel.getNumberFormats() );
bool bOwnData = false;
bool bOwnDataAnddAxisHasAnyFormat = false;
bool bOwnDataAnddAxisHasDateFormat = false;
Reference< XCoordinateSystem > xCooSysModel( ChartModelHelper::getFirstCoordinateSystem( rModel ) );
if( xCooSysModel.is() )
{
if( rModel.hasInternalDataProvider() )
{
bOwnData = true;
Reference< beans::XPropertySet > xAxisProps( xCooSysModel->getAxisByDimension(0,0), uno::UNO_QUERY );
sal_Int32 nAxisNumberFormat = 0;
if (xAxisProps.is() && (xAxisProps->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nAxisNumberFormat))
{
bOwnDataAnddAxisHasAnyFormat = true;
bOwnDataAnddAxisHasDateFormat = DiagramHelper::isDateNumberFormat( nAxisNumberFormat, xNumberFormats );
}
}
}
for(sal_Int32 nN=0;nN<nCount;nN++)
{
bool bIsDate = false;
if( bIsAutoDate )
{
if( bOwnData )
bIsDate = !bOwnDataAnddAxisHasAnyFormat || bOwnDataAnddAxisHasDateFormat;
else
bIsDate = DiagramHelper::isDateNumberFormat( xDataSequence->getNumberFormatKeyByIndex( nN ), xNumberFormats );
}
else
bIsDate = true;
bool bContainsEmptyString = false;
uno::Any aAny = aValues[nN];
if( aAny.hasValue() )
{
OUString aTest;
double fTest = 0;
bool bContainsNan = false;
if( (aAny>>=aTest) && aTest.isEmpty() ) //empty String
bContainsEmptyString = true;
else if( (aAny>>=fTest) && std::isnan(fTest) )
bContainsNan = true;
if( !bContainsEmptyString && !bContainsNan )
bAnyDataFound = true;
}
double aDate( 1.0 );
if( bIsDate && (aAny >>= aDate) )
rDateCategories.push_back( aDate );
else
{
if( aAny.hasValue() && !bContainsEmptyString )//empty string does not count as non date value!
bOnlyDatesFound=false;
::rtl::math::setNan( &aDate );
rDateCategories.push_back( aDate );
}
}
std::sort( rDateCategories.begin(), rDateCategories.end() );
}
return bAnyDataFound && bOnlyDatesFound;
}
void ExplicitCategoriesProvider::init()
{
if( !m_bDirty )
return;
m_aComplexCats.clear();//not one per index
m_aDateCategories.clear();
if( m_xOriginalCategories.is() )
{
if( !hasComplexCategories() )
{
if(m_bIsDateAxis)
{
if( ChartTypeHelper::isSupportingDateAxis( AxisHelper::getChartTypeByIndex( m_xCooSysModel, 0 ), 0 ) )
m_bIsDateAxis = lcl_fillDateCategories( m_xOriginalCategories->getValues(), m_aDateCategories, m_bIsAutoDate, mrModel );
else
m_bIsDateAxis = false;
}
}
else
{
m_bIsDateAxis = false;
}
}
else
m_bIsDateAxis=false;
m_bDirty = false;
}
Sequence< OUString > const & ExplicitCategoriesProvider::getSimpleCategories()
{
if( !m_bIsExplicitCategoriesInited )
{
init();
m_aExplicitCategories.realloc(0);
if( m_xOriginalCategories.is() )
{
if( !hasComplexCategories() )
{
uno::Reference< data::XDataSequence > xDataSequence( m_xOriginalCategories->getValues() );
if( xDataSequence.is() )
ExplicitCategoriesProvider::convertCategoryAnysToText( m_aExplicitCategories, xDataSequence->getData(), mrModel );
}
else
{
m_aExplicitCategories = lcl_getExplicitSimpleCategories(
SplitCategoriesProvider_ForLabeledDataSequences( m_aSplitCategoriesList, mrModel ), m_aComplexCats );
}
}
if(!m_aExplicitCategories.hasElements())
m_aExplicitCategories = DiagramHelper::generateAutomaticCategoriesFromCooSys( m_xCooSysModel );
m_bIsExplicitCategoriesInited = true;
}
return m_aExplicitCategories;
}
const std::vector<ComplexCategory>* ExplicitCategoriesProvider::getCategoriesByLevel( sal_Int32 nLevel )
{
init();
sal_Int32 nMaxIndex = m_aComplexCats.size()-1;
if (nLevel >= 0 && nLevel <= nMaxIndex)
return &m_aComplexCats[nMaxIndex-nLevel];
return nullptr;
}
OUString ExplicitCategoriesProvider::getCategoryByIndex(
const Reference< XCoordinateSystem >& xCooSysModel
, ChartModel& rModel
, sal_Int32 nIndex )
{
if( xCooSysModel.is())
{
ExplicitCategoriesProvider aExplicitCategoriesProvider( xCooSysModel, rModel );
Sequence< OUString > aCategories( aExplicitCategoriesProvider.getSimpleCategories());
if( nIndex < aCategories.getLength())
return aCategories[ nIndex ];
}
return OUString();
}
bool ExplicitCategoriesProvider::isDateAxis()
{
init();
return m_bIsDateAxis;
}
const std::vector< double >& ExplicitCategoriesProvider::getDateCategories()
{
init();
return m_aDateCategories;
}
} //namespace chart
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */