/* -*- 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 "SchXMLChartContext.hxx"
#include <SchXMLImport.hxx>
#include "SchXMLLegendContext.hxx"
#include "SchXMLDataTableContext.hxx"
#include "SchXMLPlotAreaContext.hxx"
#include "SchXMLParagraphContext.hxx"
#include "SchXMLTableContext.hxx"
#include "SchXMLSeries2Context.hxx"
#include "SchXMLTools.hxx"
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <unotools/mediadescriptor.hxx>
#include <utility>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/xmltoken.hxx>
#include <xmloff/namespacemap.hxx>
#include <xmloff/xmluconv.hxx>
#include <xmloff/xmlstyle.hxx>
#include <xmloff/SchXMLSeriesHelper.hxx>

#include <vector>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/chart/XChartDocument.hpp>
#include <com/sun/star/chart/XDiagram.hpp>
#include <com/sun/star/xml/sax/XAttributeList.hpp>
#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
#include <com/sun/star/drawing/XDrawPage.hpp>
#include <com/sun/star/chart/ChartDataRowSource.hpp>
#include <com/sun/star/embed/Aspects.hpp>
#include <com/sun/star/embed/XVisualObject.hpp>

#include <com/sun/star/chart2/XChartDocument.hpp>
#include <com/sun/star/chart2/data/XDataSink.hpp>
#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
#include <com/sun/star/chart2/XDataSeriesContainer.hpp>
#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
#include <com/sun/star/chart2/XChartTypeContainer.hpp>
#include <com/sun/star/chart2/XTitled.hpp>

#include <com/sun/star/container/XChild.hpp>
#include <com/sun/star/chart2/data/XDataReceiver.hpp>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>

using namespace com::sun::star;
using namespace ::xmloff::token;
using com::sun::star::uno::Reference;
using namespace ::SchXMLTools;

namespace
{

void lcl_setRoleAtLabeledSequence(
    const uno::Reference< chart2::data::XLabeledDataSequence > & xLSeq,
    const OUString &rRole )
{
    // set role of sequence
    uno::Reference< chart2::data::XDataSequence > xValues( xLSeq->getValues());
    if( xValues.is())
    {
        uno::Reference< beans::XPropertySet > xProp( xValues, uno::UNO_QUERY );
        if( xProp.is())
            xProp->setPropertyValue("Role", uno::Any( rRole ));
    }
}

void lcl_MoveDataToCandleStickSeries(
    const uno::Reference< chart2::data::XDataSource > & xDataSource,
    const uno::Reference< chart2::XDataSeries > & xDestination,
    const OUString & rRole )
{
    try
    {
        uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aLabeledSeq(
            xDataSource->getDataSequences());
        if( aLabeledSeq.hasElements())
        {
            lcl_setRoleAtLabeledSequence( aLabeledSeq[0], rRole );

            // add to data series
            uno::Reference< chart2::data::XDataSource > xSource( xDestination, uno::UNO_QUERY_THROW );
            // @todo: realloc only once outside this function
            uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aData( xSource->getDataSequences());
            aData.realloc( aData.getLength() + 1);
            aData.getArray()[ aData.getLength() - 1 ] = aLabeledSeq[0];
            uno::Reference< chart2::data::XDataSink > xSink( xDestination, uno::UNO_QUERY_THROW );
            xSink->setData( aData );
        }
    }
    catch(const uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION("xmloff.chart", "Exception caught while moving data to candlestick series" );
    }
}

void lcl_setRoleAtFirstSequence(
    const uno::Reference< chart2::XDataSeries > & xSeries,
    const OUString & rRole )
{
    uno::Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY );
    if( xSource.is())
    {
        uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aSeq( xSource->getDataSequences());
        if( aSeq.hasElements())
            lcl_setRoleAtLabeledSequence( aSeq[0], rRole );
    }
}

void lcl_removeEmptyChartTypeGroups( const uno::Reference< chart2::XChartDocument > & xDoc )
{
    if( ! xDoc.is())
        return;

    uno::Reference< chart2::XDiagram > xDia( xDoc->getFirstDiagram());
    if( ! xDia.is())
        return;

    try
    {
        // count all charttype groups to be able to leave at least one
        sal_Int32 nRemainingGroups = 0;
        uno::Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( xDia, uno::UNO_QUERY_THROW );
        const uno::Sequence< uno::Reference< chart2::XCoordinateSystem > >
            aCooSysSeq( xCooSysCnt->getCoordinateSystems());
        for( auto const & i : aCooSysSeq )
        {
            uno::Reference< chart2::XChartTypeContainer > xCTCnt( i, uno::UNO_QUERY_THROW );
            nRemainingGroups += xCTCnt->getChartTypes().getLength();
        }

        // delete all empty groups, but leave at least  group (empty or not)
        for( sal_Int32 nI = aCooSysSeq.getLength(); nI-- && (nRemainingGroups > 1); )
        {
            uno::Reference< chart2::XChartTypeContainer > xCTCnt( aCooSysSeq[nI], uno::UNO_QUERY_THROW );
            uno::Sequence< uno::Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes());
            for( sal_Int32 nJ=aCTSeq.getLength(); nJ-- && (nRemainingGroups > 1); )
            {
                uno::Reference< chart2::XDataSeriesContainer > xDSCnt( aCTSeq[nJ], uno::UNO_QUERY_THROW );
                if( !xDSCnt->getDataSeries().hasElements() )
                {
                    // note: iterator stays valid as we have a local sequence
                    xCTCnt->removeChartType( aCTSeq[nJ] );
                    --nRemainingGroups;
                }
            }
        }
    }
    catch(const uno::Exception&)
    {
        TOOLS_INFO_EXCEPTION("xmloff.chart", "Exception caught while removing empty chart types");
    }
}

uno::Sequence< sal_Int32 > lcl_getNumberSequenceFromString( std::u16string_view rStr, bool bAddOneToEachOldIndex )
{
    const sal_Unicode aSpace( ' ' );

    // count number of entries
    ::std::vector< sal_Int32 > aVec;
    size_t nLastPos = 0;
    size_t nPos = 0;
    while( nPos != std::u16string_view::npos )
    {
        nPos = rStr.find( aSpace, nLastPos );
        if( nPos != std::u16string_view::npos )
        {
            if( nPos > nLastPos )
                aVec.push_back( o3tl::toInt32(rStr.substr( nLastPos, (nPos - nLastPos) )) );
            nLastPos = nPos + 1;
        }
    }
    // last entry
    if( nLastPos != 0 &&
        rStr.size() > nLastPos )
    {
        aVec.push_back( o3tl::toInt32(rStr.substr( nLastPos )) );
    }

    const size_t nVecSize = aVec.size();
    uno::Sequence< sal_Int32 > aSeq( nVecSize );

    if(!bAddOneToEachOldIndex)
    {
        sal_Int32* pSeqArr = aSeq.getArray();
        for( nPos = 0; nPos < nVecSize; ++nPos )
        {
            pSeqArr[ nPos ] = aVec[ nPos ];
        }
    }
    else if( bAddOneToEachOldIndex )
    {
        aSeq.realloc( nVecSize+1 );
        auto pSeqArr = aSeq.getArray();
        pSeqArr[0]=0;

        for( nPos = 0; nPos < nVecSize; ++nPos )
        {
            pSeqArr[ nPos+1 ] = aVec[ nPos ]+1;
        }
    }

    return aSeq;
}

} // anonymous namespace

SchXMLChartContext::SchXMLChartContext( SchXMLImportHelper& rImpHelper,
                                        SvXMLImport& rImport ) :
        SvXMLImportContext( rImport ),
        mrImportHelper( rImpHelper ),
        m_bHasRangeAtPlotArea( false ),
        m_bHasTableElement( false ),
        mbAllRangeAddressesAvailable( true ),
        mbColHasLabels( false ),
        mbRowHasLabels( false ),
        meDataRowSource( chart::ChartDataRowSource_COLUMNS ),
        mbIsStockChart( false )
{
}

SchXMLChartContext::~SchXMLChartContext()
{}

static bool lcl_hasServiceName(Reference<lang::XMultiServiceFactory> const & xFactory, OUString const & rServiceName)
{
    const uno::Sequence<OUString> aServiceNames(xFactory->getAvailableServiceNames());

    return std::find(aServiceNames.begin(), aServiceNames.end(), rServiceName) != aServiceNames.end();
}

void setDataProvider(uno::Reference<chart2::XChartDocument> const & xChartDoc, OUString const & sDataPilotSource)
{
    if (!xChartDoc.is())
        return;

    try
    {
        uno::Reference<container::XChild> xChild(xChartDoc, uno::UNO_QUERY);
        uno::Reference<chart2::data::XDataReceiver> xDataReceiver(xChartDoc, uno::UNO_QUERY);
        if (xChild.is() && xDataReceiver.is())
        {
            bool bHasOwnData = true;

            Reference<lang::XMultiServiceFactory> xFact(xChild->getParent(), uno::UNO_QUERY);
            if (xFact.is())
            {
                if (!xChartDoc->getDataProvider().is())
                {
                    bool bHasDataPilotSource = !sDataPilotSource.isEmpty();
                    OUString aDataProviderServiceName("com.sun.star.chart2.data.DataProvider");
                    if (bHasDataPilotSource)
                        aDataProviderServiceName = "com.sun.star.chart2.data.PivotTableDataProvider";

                    if (lcl_hasServiceName(xFact, aDataProviderServiceName))
                    {
                        Reference<chart2::data::XDataProvider> xProvider(xFact->createInstance(aDataProviderServiceName), uno::UNO_QUERY);

                        if (xProvider.is())
                        {
                            if (bHasDataPilotSource)
                            {
                                Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(xProvider, uno::UNO_QUERY);
                                xPivotTableDataProvider->setPivotTableName(sDataPilotSource);
                                xDataReceiver->attachDataProvider(xProvider);
                                bHasOwnData = !xPivotTableDataProvider->hasPivotTable();
                            }
                            else
                            {
                                xDataReceiver->attachDataProvider(xProvider);
                                bHasOwnData = false;
                            }
                        }
                    }
                }
                else
                    bHasOwnData = false;
            }
            // else we have no parent => we have our own data

            if (bHasOwnData && ! xChartDoc->hasInternalDataProvider())
                xChartDoc->createInternalDataProvider(false);
        }
    }
    catch (const uno::Exception &)
    {
        TOOLS_INFO_EXCEPTION("xmloff.chart", "SchXMLChartContext::StartElement()");
    }
}

void SchXMLChartContext::startFastElement( sal_Int32 /*nElement*/,
    const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
{
    // parse attributes

    uno::Reference< embed::XVisualObject > xVisualObject( mrImportHelper.GetChartDocument(), uno::UNO_QUERY);
    SAL_WARN_IF(!xVisualObject.is(), "xmloff.chart", "need xVisualObject for page size");
    if( xVisualObject.is() )
        maChartSize = xVisualObject->getVisualAreaSize( embed::Aspects::MSOLE_CONTENT ); //#i103460# take the size given from the parent frame as default

    OUString sAutoStyleName;
    OUString aOldChartTypeName;
    bool bHasAddin = false;

    for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
    {
        switch( aIter.getToken() )
        {
            case XML_ELEMENT(LO_EXT, XML_DATA_PILOT_SOURCE):
                msDataPilotSource = aIter.toString();
                break;
            case XML_ELEMENT(XLINK, XML_HREF):
                m_aXLinkHRefAttributeToIndicateDataProvider = aIter.toString();
                break;
            case XML_ELEMENT(CHART, XML_CLASS):
                {
                    OUString aValue = aIter.toString();
                    OUString sClassName;
                    sal_uInt16 nClassPrefix =
                        GetImport().GetNamespaceMap().GetKeyByAttrValueQName(
                                aValue, &sClassName );
                    if( XML_NAMESPACE_CHART == nClassPrefix )
                    {
                        SchXMLChartTypeEnum eChartTypeEnum = SchXMLTools::GetChartTypeEnum( sClassName );
                        if( eChartTypeEnum != XML_CHART_CLASS_UNKNOWN )
                        {
                            aOldChartTypeName = SchXMLTools::GetChartTypeByClassName( sClassName, true /* bUseOldNames */ );
                            maChartTypeServiceName = SchXMLTools::GetChartTypeByClassName( sClassName, false /* bUseOldNames */ );
                            switch( eChartTypeEnum )
                            {
                            case XML_CHART_CLASS_STOCK:
                                mbIsStockChart = true;
                                break;
                            default:
                                break;
                            }
                        }
                    }
                    else if( XML_NAMESPACE_OOO == nClassPrefix )
                    {
                        // service is taken from add-in-name attribute
                        bHasAddin = true;

                        aOldChartTypeName = sClassName;
                        maChartTypeServiceName = sClassName;
                    }
                }
                break;

            case XML_ELEMENT(SVG, XML_WIDTH):
            case XML_ELEMENT(SVG_COMPAT, XML_WIDTH):
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        maChartSize.Width, aIter.toView() );
                break;

            case XML_ELEMENT(SVG, XML_HEIGHT):
            case XML_ELEMENT(SVG_COMPAT, XML_HEIGHT):
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        maChartSize.Height, aIter.toView() );
                break;

            case XML_ELEMENT(CHART, XML_STYLE_NAME):
                sAutoStyleName = aIter.toString();
                break;

            case XML_ELEMENT(CHART, XML_COLUMN_MAPPING):
                msColTrans = aIter.toString();
                break;
            case XML_ELEMENT(CHART,  XML_ROW_MAPPING):
                msRowTrans = aIter.toString();
                break;
            default:
                XMLOFF_WARN_UNKNOWN("xmloff", aIter);
        }
    }

    uno::Reference<chart::XChartDocument> xDoc = mrImportHelper.GetChartDocument();
    uno::Reference<chart2::XChartDocument> xNewDoc(xDoc, uno::UNO_QUERY);

    setDataProvider(xNewDoc, msDataPilotSource);

    if( aOldChartTypeName.isEmpty() )
    {
        SAL_WARN("xmloff.chart", "need a charttype to create a diagram" );
        //set a fallback value:
        const OUString& aChartClass_Bar( GetXMLToken(XML_BAR ) );
        aOldChartTypeName = SchXMLTools::GetChartTypeByClassName( aChartClass_Bar, true /* bUseOldNames */ );
        maChartTypeServiceName = SchXMLTools::GetChartTypeByClassName( aChartClass_Bar, false /* bUseOldNames */ );
    }

    //  Set the size of the draw page.
    if( xVisualObject.is() )
        xVisualObject->setVisualAreaSize( embed::Aspects::MSOLE_CONTENT, maChartSize );

    InitChart( aOldChartTypeName);

    if( bHasAddin )
    {
        //correct charttype service name when having an addin
        //and don't refresh addin during load
        uno::Reference< beans::XPropertySet > xDocProp( mrImportHelper.GetChartDocument(), uno::UNO_QUERY );
        if( xDocProp.is() )
        {
            try
            {
                xDocProp->getPropertyValue("BaseDiagram") >>= aOldChartTypeName;
                maChartTypeServiceName =  SchXMLTools::GetNewChartTypeName( aOldChartTypeName );
                xDocProp->setPropertyValue("RefreshAddInAllowed", uno::Any( false) );
            }
            catch(const uno::Exception&)
            {
                TOOLS_WARN_EXCEPTION("xmloff.chart", "Exception during import SchXMLChartContext::StartElement" );
            }
        }
    }

    // set auto-styles for Area
    uno::Reference<beans::XPropertySet> xProp = mrImportHelper.GetChartDocument()->getArea();
    mrImportHelper.FillAutoStyle(sAutoStyleName, xProp);
}

namespace
{

struct NewDonutSeries
{
    css::uno::Reference< css::chart2::XDataSeries > m_xSeries;
    OUString msStyleName;
    sal_Int32 mnAttachedAxis;

    ::std::vector< OUString > m_aSeriesStyles;
    ::std::vector< OUString > m_aPointStyles;

    NewDonutSeries( css::uno::Reference< css::chart2::XDataSeries > xSeries, sal_Int32 nPointCount )
                    : m_xSeries(std::move( xSeries ))
                    , mnAttachedAxis( 1 )
    {
        m_aPointStyles.resize(nPointCount);
        m_aSeriesStyles.resize(nPointCount);
    }

    void setSeriesStyleNameToPoint( const OUString& rStyleName, sal_Int32 nPointIndex )
    {
        SAL_WARN_IF(nPointIndex >= static_cast<sal_Int32>(m_aSeriesStyles.size()), "xmloff.chart", "donut point <-> series count mismatch");
        if( nPointIndex < static_cast<sal_Int32>(m_aSeriesStyles.size()) )
            m_aSeriesStyles[nPointIndex]=rStyleName;
    }

    void setPointStyleNameToPoint( const OUString& rStyleName, sal_Int32 nPointIndex )
    {
        SAL_WARN_IF(nPointIndex >= static_cast<sal_Int32>(m_aPointStyles.size()), "xmloff.chart", "donut point <-> series count mismatch");
        if( nPointIndex < static_cast<sal_Int32>(m_aPointStyles.size()) )
            m_aPointStyles[nPointIndex]=rStyleName;
    }

    ::std::vector< DataRowPointStyle > creatStyleVector()
    {
        ::std::vector< DataRowPointStyle > aRet;

        DataRowPointStyle aSeriesStyle( DataRowPointStyle::DATA_SERIES
            , m_xSeries, -1, 1, msStyleName, mnAttachedAxis );
        aRet.push_back( aSeriesStyle );

        sal_Int32 nPointIndex=0;
        for (auto const& pointStyle : m_aPointStyles)
        {
            DataRowPointStyle aPointStyle( DataRowPointStyle::DATA_POINT
                , m_xSeries, nPointIndex, 1, pointStyle, mnAttachedAxis );
            if( nPointIndex < static_cast<sal_Int32>(m_aSeriesStyles.size()) )
            {
                aPointStyle.msSeriesStyleNameForDonuts = m_aSeriesStyles[nPointIndex];
            }
            if( !aPointStyle.msSeriesStyleNameForDonuts.isEmpty()
                || !aPointStyle.msStyleName.isEmpty() )
                aRet.push_back( aPointStyle );
            ++nPointIndex;
        }

        return aRet;
    }
};

void lcl_swapPointAndSeriesStylesForDonutCharts( ::std::vector< DataRowPointStyle >& rStyleVector
        , ::std::map< css::uno::Reference< css::chart2::XDataSeries> , sal_Int32 >&& aSeriesMap )
{
    //detect old series count
    //and add old series to aSeriesMap
    sal_Int32 nOldSeriesCount = 0;
    {
        sal_Int32 nMaxOldSeriesIndex = 0;
        sal_Int32 nOldSeriesIndex = 0;
        for (auto const& style : rStyleVector)
        {
            DataRowPointStyle aStyle(style);
            if(aStyle.meType == DataRowPointStyle::DATA_SERIES &&
                    aStyle.m_xSeries.is() )
            {
                nMaxOldSeriesIndex = nOldSeriesIndex;

                if( aSeriesMap.end() == aSeriesMap.find(aStyle.m_xSeries) )
                    aSeriesMap[aStyle.m_xSeries] = nOldSeriesIndex;

                nOldSeriesIndex++;
            }
        }
        nOldSeriesCount = nMaxOldSeriesIndex+1;
    }

    //initialize new series styles
    ::std::map< Reference< chart2::XDataSeries >, sal_Int32 >::const_iterator aSeriesMapEnd( aSeriesMap.end() );

    //sort by index
    ::std::vector< NewDonutSeries > aNewSeriesVector;
    {
        ::std::map< sal_Int32, Reference< chart2::XDataSeries > > aIndexSeriesMap;
        for (auto const& series : aSeriesMap)
            aIndexSeriesMap[series.second] = series.first;

        for (auto const& indexSeries : aIndexSeriesMap)
            aNewSeriesVector.emplace_back(indexSeries.second,nOldSeriesCount );
    }

    //overwrite attached axis information according to old series styles
    for (auto const& style : rStyleVector)
    {
        DataRowPointStyle aStyle(style);
        if(aStyle.meType == DataRowPointStyle::DATA_SERIES )
        {
            auto aSeriesMapIt = aSeriesMap.find( aStyle.m_xSeries );
            if( aSeriesMapIt != aSeriesMapEnd && aSeriesMapIt->second < static_cast<sal_Int32>(aNewSeriesVector.size()) )
                aNewSeriesVector[aSeriesMapIt->second].mnAttachedAxis = aStyle.mnAttachedAxis;
        }
    }

    //overwrite new series style names with old series style name information
    for (auto const& style : rStyleVector)
    {
        DataRowPointStyle aStyle(style);
        if( aStyle.meType == DataRowPointStyle::DATA_SERIES )
        {
            auto aSeriesMapIt = aSeriesMap.find(aStyle.m_xSeries);
            if( aSeriesMapEnd != aSeriesMapIt )
            {
                sal_Int32 nNewPointIndex = aSeriesMapIt->second;

                for (auto & newSeries : aNewSeriesVector)
                    newSeries.setSeriesStyleNameToPoint( aStyle.msStyleName, nNewPointIndex );
            }
        }
    }

    //overwrite new series style names with point style name information
    for (auto const& style : rStyleVector)
    {
        DataRowPointStyle aStyle(style);
        if( aStyle.meType == DataRowPointStyle::DATA_POINT )
        {
            auto aSeriesMapIt = aSeriesMap.find(aStyle.m_xSeries);
            if( aSeriesMapEnd != aSeriesMapIt )
            {
                sal_Int32 nNewPointIndex = aSeriesMapIt->second;
                sal_Int32 nNewSeriesIndex = aStyle.m_nPointIndex;
                sal_Int32 nRepeatCount = aStyle.m_nPointRepeat;

                while( nRepeatCount && (nNewSeriesIndex>=0) && (o3tl::make_unsigned(nNewSeriesIndex)< aNewSeriesVector.size() ) )
                {
                    NewDonutSeries& rNewSeries( aNewSeriesVector[nNewSeriesIndex] );
                    rNewSeries.setPointStyleNameToPoint( aStyle.msStyleName, nNewPointIndex );

                    nRepeatCount--;
                    nNewSeriesIndex++;
                }
            }
        }
    }

    //put information from aNewSeriesVector to output parameter rStyleVector
    rStyleVector.clear();

    for (auto & newSeries : aNewSeriesVector)
    {
        ::std::vector< DataRowPointStyle > aVector( newSeries.creatStyleVector() );
        rStyleVector.insert(rStyleVector.end(),aVector.begin(),aVector.end());
    }
}

bool lcl_SpecialHandlingForDonutChartNeeded(
    std::u16string_view rServiceName,
    const SvXMLImport & rImport )
{
    bool bResult = false;
    if( rServiceName == u"com.sun.star.chart2.DonutChartType" )
    {
        bResult = SchXMLTools::isDocumentGeneratedWithOpenOfficeOlderThan2_3( rImport.GetModel() );
    }
    return bResult;
}

} // anonymous namespace

static void lcl_ApplyDataFromRectangularRangeToDiagram(
        const uno::Reference< chart2::XChartDocument >& xNewDoc
        , const OUString& rRectangularRange
        , css::chart::ChartDataRowSource eDataRowSource
        , bool bRowHasLabels, bool bColHasLabels
        , bool bSwitchOnLabelsAndCategoriesForOwnData
        , std::u16string_view sColTrans
        , std::u16string_view sRowTrans )
{
    if( !xNewDoc.is() )
        return;

    uno::Reference< chart2::XDiagram > xNewDia( xNewDoc->getFirstDiagram());
    uno::Reference< chart2::data::XDataProvider > xDataProvider( xNewDoc->getDataProvider() );
    if( !xNewDia.is() || !xDataProvider.is() )
        return;

    bool bFirstCellAsLabel =
        (eDataRowSource==chart::ChartDataRowSource_COLUMNS)? bRowHasLabels : bColHasLabels;
    bool bHasCateories =
        (eDataRowSource==chart::ChartDataRowSource_COLUMNS)? bColHasLabels : bRowHasLabels;

    if( bSwitchOnLabelsAndCategoriesForOwnData )
    {
        bFirstCellAsLabel = true;
        bHasCateories = true;
    }

    uno::Sequence< beans::PropertyValue > aArgs{
        beans::PropertyValue(
           "CellRangeRepresentation",
           -1, uno::Any( rRectangularRange ),
           beans::PropertyState_DIRECT_VALUE ),
        beans::PropertyValue(
           "DataRowSource",
           -1, uno::Any( eDataRowSource ),
           beans::PropertyState_DIRECT_VALUE ),
        beans::PropertyValue(
           "FirstCellAsLabel",
           -1, uno::Any( bFirstCellAsLabel ),
           beans::PropertyState_DIRECT_VALUE )
    };

    if( !sColTrans.empty() || !sRowTrans.empty() )
    {
        aArgs.realloc( aArgs.getLength() + 1 );
        aArgs.getArray()[ sal::static_int_cast<sal_uInt32>(aArgs.getLength()) - 1 ] = beans::PropertyValue(
            "SequenceMapping",
            -1, uno::Any( !sColTrans.empty()
                ? lcl_getNumberSequenceFromString( sColTrans, bHasCateories && !xNewDoc->hasInternalDataProvider() )
                : lcl_getNumberSequenceFromString( sRowTrans, bHasCateories && !xNewDoc->hasInternalDataProvider() ) ),
        beans::PropertyState_DIRECT_VALUE );
    }

    //work around wrong writer ranges ( see Issue 58464 )
    {
        OUString aChartOleObjectName;
        if( xNewDoc.is() )
        {
            utl::MediaDescriptor aMediaDescriptor( xNewDoc->getArgs() );

            utl::MediaDescriptor::const_iterator aIt(
                aMediaDescriptor.find( OUString(  "HierarchicalDocumentName" )));
            if( aIt != aMediaDescriptor.end() )
            {
                aChartOleObjectName = (*aIt).second.get< OUString >();
            }
        }
        if( !aChartOleObjectName.isEmpty() )
        {
            aArgs.realloc( aArgs.getLength() + 1 );
            aArgs.getArray()[ sal::static_int_cast<sal_uInt32>(aArgs.getLength()) - 1 ] = beans::PropertyValue(
                "ChartOleObjectName",
                -1, uno::Any( aChartOleObjectName ),
                beans::PropertyState_DIRECT_VALUE );
        }
    }

    uno::Reference< chart2::data::XDataSource > xDataSource(
        xDataProvider->createDataSource( aArgs ));

    aArgs.realloc( aArgs.getLength() + 2 );
    auto pArgs = aArgs.getArray();
    pArgs[ sal::static_int_cast<sal_uInt32>(aArgs.getLength()) - 2 ] = beans::PropertyValue(
        "HasCategories",
        -1, uno::Any( bHasCateories ),
        beans::PropertyState_DIRECT_VALUE );
    pArgs[ sal::static_int_cast<sal_uInt32>(aArgs.getLength()) - 1 ] = beans::PropertyValue(
        "UseCategoriesAsX",
        -1, uno::Any( false ),//categories in ODF files are not to be used as x values (independent from what is offered in our ui)
        beans::PropertyState_DIRECT_VALUE );

    xNewDia->setDiagramData( xDataSource, aArgs );
}

void SchXMLChartContext::endFastElement(sal_Int32 )
{
    uno::Reference< chart::XChartDocument > xDoc = mrImportHelper.GetChartDocument();
    uno::Reference< beans::XPropertySet > xProp( xDoc, uno::UNO_QUERY );
    uno::Reference< chart2::XChartDocument > xNewDoc( xDoc, uno::UNO_QUERY );

    if( xProp.is())
    {
        if( !maMainTitle.isEmpty())
        {
            uno::Reference< beans::XPropertySet > xTitleProp( xDoc->getTitle(), uno::UNO_QUERY );
            if( xTitleProp.is())
            {
                try
                {
                    xTitleProp->setPropertyValue("String", uno::Any(maMainTitle) );
                }
                catch(const beans::UnknownPropertyException&)
                {
                    SAL_WARN("xmloff.chart", "Property String for Title not available" );
                }
            }
        }
        if( !maSubTitle.isEmpty())
        {
            uno::Reference< beans::XPropertySet > xTitleProp( xDoc->getSubTitle(), uno::UNO_QUERY );
            if( xTitleProp.is())
            {
                try
                {
                    xTitleProp->setPropertyValue("String", uno::Any(maSubTitle) );
                }
                catch(const beans::UnknownPropertyException&)
                {
                    SAL_WARN("xmloff.chart", "Property String for Title not available" );
                }
            }
        }
    }

    // cleanup: remove empty chart type groups
    lcl_removeEmptyChartTypeGroups( xNewDoc );

    // set stack mode before a potential chart type detection (in case we have a rectangular range)
    uno::Reference< chart::XDiagram > xDiagram( xDoc->getDiagram() );
    uno::Reference< beans::XPropertySet > xDiaProp( xDiagram, uno::UNO_QUERY );
    if( xDiaProp.is())
    {
        if( maSeriesDefaultsAndStyles.maStackedDefault.hasValue())
            xDiaProp->setPropertyValue("Stacked",maSeriesDefaultsAndStyles.maStackedDefault);
        if( maSeriesDefaultsAndStyles.maPercentDefault.hasValue())
            xDiaProp->setPropertyValue("Percent",maSeriesDefaultsAndStyles.maPercentDefault);
        if( maSeriesDefaultsAndStyles.maDeepDefault.hasValue())
            xDiaProp->setPropertyValue("Deep",maSeriesDefaultsAndStyles.maDeepDefault);
        if( maSeriesDefaultsAndStyles.maStackedBarsConnectedDefault.hasValue())
            xDiaProp->setPropertyValue("StackedBarsConnected",maSeriesDefaultsAndStyles.maStackedBarsConnectedDefault);
    }

    //the OOo 2.0 implementation and older has a bug with donuts
    bool bSpecialHandlingForDonutChart = lcl_SpecialHandlingForDonutChartNeeded(
        maChartTypeServiceName, GetImport());

    // apply data
    if(!xNewDoc.is())
        return;

    bool bHasOwnData = false;
    if( m_aXLinkHRefAttributeToIndicateDataProvider == "." ) //data comes from the chart itself
        bHasOwnData = true;
    else if( m_aXLinkHRefAttributeToIndicateDataProvider == ".." ) //data comes from the parent application
        bHasOwnData = false;
    else if( !m_aXLinkHRefAttributeToIndicateDataProvider.isEmpty() ) //not supported so far to get the data by sibling objects -> fall back to chart itself if data are available
        bHasOwnData = m_bHasTableElement;
    else
        bHasOwnData = !m_bHasRangeAtPlotArea;

    if( xNewDoc->hasInternalDataProvider())
    {
        if( !m_bHasTableElement && m_aXLinkHRefAttributeToIndicateDataProvider != "." )
        {
            //#i103147# ODF, workaround broken files with a missing table:cell-range-address at the plot-area
            bool bSwitchSuccessful = SchXMLTools::switchBackToDataProviderFromParent( xNewDoc, maLSequencesPerIndex );
            bHasOwnData = !bSwitchSuccessful;
        }
        else
            bHasOwnData = true;//e.g. in case of copy->paste from calc to impress
    }
    else if( bHasOwnData )
    {
        xNewDoc->createInternalDataProvider( false /* bCloneExistingData */ );
    }
    if( bHasOwnData )
        msChartAddress = "all";

    bool bSwitchRangesFromOuterToInternalIfNecessary = false;
    if( !bHasOwnData && mbAllRangeAddressesAvailable )
    {
        // special handling for stock chart (merge series together)
        if( mbIsStockChart )
            MergeSeriesForStockChart();
    }
    else if( !msChartAddress.isEmpty() )
    {
        //own data or only rectangular range available

        if( xNewDoc->hasInternalDataProvider() )
            SchXMLTableHelper::applyTableToInternalDataProvider( maTable, xNewDoc );

        bool bOlderThan2_3 = SchXMLTools::isDocumentGeneratedWithOpenOfficeOlderThan2_3( xNewDoc );
        bool bOldFileWithOwnDataFromRows = (bOlderThan2_3 && bHasOwnData && (meDataRowSource==chart::ChartDataRowSource_ROWS)); // in this case there are range addresses that are simply wrong.

        if( mbAllRangeAddressesAvailable && !bSpecialHandlingForDonutChart && !mbIsStockChart &&
            !bOldFileWithOwnDataFromRows )
        {
            //bHasOwnData is true in this case!
            //e.g. for normal files with own data or also in case of copy paste scenario (e.g. calc to impress)
            bSwitchRangesFromOuterToInternalIfNecessary = true;
        }
        else
        {
            //apply data from rectangular range

            // create datasource from data provider with rectangular range parameters and change the diagram setDiagramData
            try
            {
                if( bOlderThan2_3 && xDiaProp.is() )//for older charts the hidden cells were removed by calc on the fly
                    xDiaProp->setPropertyValue("IncludeHiddenCells",uno::Any(false));

                // note: mbRowHasLabels means the first row contains labels, that means we have "column-descriptions",
                // (analogously mbColHasLabels means we have "row-descriptions")
                lcl_ApplyDataFromRectangularRangeToDiagram( xNewDoc, msChartAddress, meDataRowSource, mbRowHasLabels, mbColHasLabels, bHasOwnData, msColTrans, msRowTrans );
            }
            catch(const uno::Exception&)
            {
                //try to fallback to internal data
                TOOLS_WARN_EXCEPTION("xmloff.chart", "Exception during import SchXMLChartContext::lcl_ApplyDataFromRectangularRangeToDiagram try to fallback to internal data" );
                if(!bHasOwnData)
                {
                    bHasOwnData = true;
                    msChartAddress = "all";
                    if( !xNewDoc->hasInternalDataProvider() )
                    {
                        xNewDoc->createInternalDataProvider( false /* bCloneExistingData */ );
                        SchXMLTableHelper::applyTableToInternalDataProvider( maTable, xNewDoc );
                        try
                        {
                            lcl_ApplyDataFromRectangularRangeToDiagram( xNewDoc, msChartAddress, meDataRowSource, mbRowHasLabels, mbColHasLabels, bHasOwnData, msColTrans, msRowTrans );
                        }
                        catch(const uno::Exception&)
                        {
                            TOOLS_WARN_EXCEPTION("xmloff.chart", "Exception during import SchXMLChartContext::lcl_ApplyDataFromRectangularRangeToDiagram fallback to internal data failed also" );
                        }
                    }
                }
            }
        }
    }
    else
    {
        SAL_WARN("xmloff.chart", "Must not get here" );
    }

    // now all series and data point properties are available and can be set
    {
        if( bSpecialHandlingForDonutChart )
        {
            uno::Reference< chart2::XDiagram > xNewDiagram( xNewDoc->getFirstDiagram() );
            lcl_swapPointAndSeriesStylesForDonutCharts( maSeriesDefaultsAndStyles.maSeriesStyleVector
                , SchXMLSeriesHelper::getDataSeriesIndexMapFromDiagram(xNewDiagram) );
        }

        SchXMLSeries2Context::initSeriesPropertySets( maSeriesDefaultsAndStyles, xDoc );

        //set defaults from diagram to the new series:
        //check whether we need to remove lines from symbol only charts
        bool bSwitchOffLinesForScatter = false;
        {
            bool bLinesOn = true;
            if( (maSeriesDefaultsAndStyles.maLinesOnProperty >>= bLinesOn) && !bLinesOn )
            {
                if( maChartTypeServiceName == "com.sun.star.chart2.ScatterChartType" )
                {
                    bSwitchOffLinesForScatter = true;
                    SchXMLSeries2Context::switchSeriesLinesOff( maSeriesDefaultsAndStyles.maSeriesStyleVector );
                }
            }
        }
        SchXMLSeries2Context::setDefaultsToSeries( maSeriesDefaultsAndStyles );

        // set autostyles for series and data points
        const SvXMLStylesContext* pStylesCtxt = mrImportHelper.GetAutoStylesContext();
        const SvXMLStyleContext* pStyle = nullptr;
        OUString sCurrStyleName;

        if( pStylesCtxt )
        {
            //iterate over data-series first
            //don't set series styles for donut charts
            if( !bSpecialHandlingForDonutChart )
            {
                SchXMLSeries2Context::setStylesToSeries(
                                        maSeriesDefaultsAndStyles, pStylesCtxt, pStyle,
                                        sCurrStyleName, mrImportHelper, GetImport(),
                                        mbIsStockChart, maLSequencesPerIndex );
                // ... then set attributes for statistics (after their existence was set in the series)
                SchXMLSeries2Context::setStylesToStatisticsObjects(
                                        maSeriesDefaultsAndStyles, pStylesCtxt,
                                        pStyle, sCurrStyleName );

                SchXMLSeries2Context::setStylesToRegressionCurves(
                                        maSeriesDefaultsAndStyles, pStylesCtxt,
                                        pStyle, sCurrStyleName );
            }
        }

        //#i98319# call switchRangesFromOuterToInternalIfNecessary before the data point styles are applied, otherwise in copy->paste scenario the data point styles do get lost
        if( bSwitchRangesFromOuterToInternalIfNecessary )
        {
            if( xNewDoc->hasInternalDataProvider() )
                SchXMLTableHelper::switchRangesFromOuterToInternalIfNecessary( maTable, maLSequencesPerIndex, xNewDoc, meDataRowSource );
        }

        if( pStylesCtxt )
        {
            // ... then iterate over data-point attributes, so the latter are not overwritten
            SchXMLSeries2Context::setStylesToDataPoints( maSeriesDefaultsAndStyles
                            , pStylesCtxt, pStyle, sCurrStyleName, mrImportHelper, GetImport(), mbIsStockChart, bSpecialHandlingForDonutChart, bSwitchOffLinesForScatter );
        }
    }

    if( xProp.is())
        xProp->setPropertyValue("RefreshAddInAllowed", uno::Any( true) );
}

void SchXMLChartContext::MergeSeriesForStockChart()
{
    OSL_ASSERT( mbIsStockChart );
    try
    {
        uno::Reference< chart::XChartDocument > xOldDoc( mrImportHelper.GetChartDocument());
        uno::Reference< chart2::XChartDocument > xDoc( xOldDoc, uno::UNO_QUERY_THROW );
        uno::Reference< chart2::XDiagram > xDiagram( xDoc->getFirstDiagram());
        if( ! xDiagram.is())
            return;

        bool bHasJapaneseCandlestick = true;
        uno::Reference< chart2::XDataSeriesContainer > xDSContainer;
        uno::Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( xDiagram, uno::UNO_QUERY_THROW );
        const uno::Sequence< uno::Reference< chart2::XCoordinateSystem > > aCooSysSeq( xCooSysCnt->getCoordinateSystems());
        for( const auto& rCooSys : aCooSysSeq )
        {
            uno::Reference< chart2::XChartTypeContainer > xCTCnt( rCooSys, uno::UNO_QUERY_THROW );
            const uno::Sequence< uno::Reference< chart2::XChartType > > aChartTypes( xCTCnt->getChartTypes());
            auto pChartType = std::find_if(aChartTypes.begin(), aChartTypes.end(),
                [](const auto& rChartType) { return rChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType"; });
            if (pChartType != aChartTypes.end())
            {
                xDSContainer.set( *pChartType, uno::UNO_QUERY_THROW );
                uno::Reference< beans::XPropertySet > xCTProp( *pChartType, uno::UNO_QUERY_THROW );
                xCTProp->getPropertyValue("Japanese") >>= bHasJapaneseCandlestick;
            }
        }

        if( xDSContainer.is())
        {
            // with japanese candlesticks: open, low, high, close
            // otherwise: low, high, close
            uno::Sequence< uno::Reference< chart2::XDataSeries > > aSeriesSeq( xDSContainer->getDataSeries());
            const sal_Int32 nSeriesCount( aSeriesSeq.getLength());
            const sal_Int32 nSeriesPerCandleStick = bHasJapaneseCandlestick ? 4: 3;
            sal_Int32 nCandleStickCount = nSeriesCount / nSeriesPerCandleStick;
            OSL_ASSERT( nSeriesPerCandleStick * nCandleStickCount == nSeriesCount );
            uno::Sequence< uno::Reference< chart2::XDataSeries > > aNewSeries( nCandleStickCount );
            auto aNewSeriesRange = asNonConstRange(aNewSeries);
            for( sal_Int32 i=0; i<nCandleStickCount; ++i )
            {
                sal_Int32 nSeriesIndex = i*nSeriesPerCandleStick;
                if( bHasJapaneseCandlestick )
                {
                    // open values
                    lcl_setRoleAtFirstSequence( aSeriesSeq[ nSeriesIndex ], "values-first");
                    aNewSeriesRange[i] = aSeriesSeq[ nSeriesIndex ];
                    // low values
                    lcl_MoveDataToCandleStickSeries(
                        uno::Reference< chart2::data::XDataSource >( aSeriesSeq[ ++nSeriesIndex ], uno::UNO_QUERY_THROW ),
                        aNewSeries[i], "values-min");
                }
                else
                {
                    // low values
                    lcl_setRoleAtFirstSequence( aSeriesSeq[ nSeriesIndex ], "values-min");
                    aNewSeriesRange[i] = aSeriesSeq[ nSeriesIndex ];
                }
                // high values
                lcl_MoveDataToCandleStickSeries(
                    uno::Reference< chart2::data::XDataSource >( aSeriesSeq[ ++nSeriesIndex ], uno::UNO_QUERY_THROW ),
                    aNewSeries[i], "values-max");
                // close values
                lcl_MoveDataToCandleStickSeries(
                    uno::Reference< chart2::data::XDataSource >( aSeriesSeq[ ++nSeriesIndex ], uno::UNO_QUERY_THROW ),
                    aNewSeries[i], "values-last");
            }
            xDSContainer->setDataSeries( aNewSeries );
        }
    }
    catch(const uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION("xmloff.chart", "Exception while merging series for stock chart" );
    }
}

css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLChartContext::createFastChildContext(
    sal_Int32 nElement,
    const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
{
    SvXMLImportContext* pContext = nullptr;
    uno::Reference< chart::XChartDocument > xDoc = mrImportHelper.GetChartDocument();
    uno::Reference< beans::XPropertySet > xProp( xDoc, uno::UNO_QUERY );

    switch(nElement)
    {
        case XML_ELEMENT(CHART, XML_PLOT_AREA):
            pContext = new SchXMLPlotAreaContext( mrImportHelper, GetImport(),
                                                  m_aXLinkHRefAttributeToIndicateDataProvider,
                                                  msCategoriesAddress,
                                                  msChartAddress, m_bHasRangeAtPlotArea, mbAllRangeAddressesAvailable,
                                                  mbColHasLabels, mbRowHasLabels,
                                                  meDataRowSource,
                                                  maSeriesDefaultsAndStyles,
                                                  maChartTypeServiceName,
                                                  maLSequencesPerIndex, maChartSize );
            break;
        case XML_ELEMENT(CHART, XML_TITLE):
            if( xDoc.is())
            {
                if( xProp.is())
                {
                    xProp->setPropertyValue("HasMainTitle", uno::Any(true) );
                }
                uno::Reference< drawing::XShape > xTitleShape = xDoc->getTitle();
                pContext = new SchXMLTitleContext( mrImportHelper, GetImport(),
                                                   maMainTitle, xTitleShape );
            }
            break;
        case XML_ELEMENT(CHART, XML_SUBTITLE):
            if( xDoc.is())
            {
                if( xProp.is())
                {
                    xProp->setPropertyValue("HasSubTitle", uno::Any(true) );
                }
                uno::Reference< drawing::XShape > xTitleShape = xDoc->getSubTitle();
                pContext = new SchXMLTitleContext( mrImportHelper, GetImport(),
                                                   maSubTitle, xTitleShape );
            }
            break;
        case XML_ELEMENT(CHART, XML_LEGEND):
            pContext = new SchXMLLegendContext( mrImportHelper, GetImport() );
            break;
        case XML_ELEMENT(LO_EXT, XML_DATA_TABLE):
            pContext = new SchXMLDataTableContext(mrImportHelper, GetImport());
            break;
        case XML_ELEMENT(TABLE, XML_TABLE):
            {
                SchXMLTableContext * pTableContext =
                    new SchXMLTableContext( GetImport(), maTable );
                m_bHasTableElement = true;
                // #i85913# take into account column- and row- mapping for
                // charts with own data only for those which were not copied
                // from a place where they got data from the container.  Note,
                // that this requires the plot-area been read before the table
                // (which is required in the ODF spec)
                // Note: For stock charts and donut charts with special handling
                // the mapping must not be applied!
                if( msChartAddress.isEmpty() && !mbIsStockChart &&
                    !lcl_SpecialHandlingForDonutChartNeeded(
                        maChartTypeServiceName, GetImport()))
                {
                    if( !msColTrans.isEmpty() )
                    {
                        OSL_ASSERT( msRowTrans.isEmpty() );
                        pTableContext->setColumnPermutation( lcl_getNumberSequenceFromString( msColTrans, true ));
                        msColTrans.clear();
                    }
                    else if( !msRowTrans.isEmpty() )
                    {
                        pTableContext->setRowPermutation( lcl_getNumberSequenceFromString( msRowTrans, true ));
                        msRowTrans.clear();
                    }
                }
                pContext = pTableContext;
            }
            break;

        default:
            // try importing as an additional shape
            if( ! mxDrawPage.is())
            {
                uno::Reference< drawing::XDrawPageSupplier  > xSupp( xDoc, uno::UNO_QUERY );
                if( xSupp.is())
                    mxDrawPage = xSupp->getDrawPage();

                SAL_WARN_IF( !mxDrawPage.is(), "xmloff.chart", "Invalid Chart Page" );
            }
            if( mxDrawPage.is())
                pContext = XMLShapeImportHelper::CreateGroupChildContext(
                    GetImport(), nElement, xAttrList, mxDrawPage );
            break;
    }

    return pContext;
}

/*
    With a locked controller the following is done here:
        1.  Hide title, subtitle, and legend.
        2.  Set the size of the draw page.
        3.  Set a (logically) empty data set.
        4.  Set the chart type.
*/
void SchXMLChartContext::InitChart(
    const OUString & rChartTypeServiceName // currently the old service name
    )
{
    uno::Reference< chart::XChartDocument > xDoc = mrImportHelper.GetChartDocument();
    SAL_WARN_IF( !xDoc.is(), "xmloff.chart", "No valid document!" );

    // Remove Title and Diagram ("De-InitNew")
    uno::Reference< chart2::XChartDocument > xNewDoc( mrImportHelper.GetChartDocument(), uno::UNO_QUERY );
    if( xNewDoc.is())
    {
        xNewDoc->setFirstDiagram( nullptr );
        uno::Reference< chart2::XTitled > xTitled( xNewDoc, uno::UNO_QUERY );
        if( xTitled.is())
            xTitled->setTitleObject( nullptr );
    }

    //  Set the chart type via setting the diagram.
    if( !rChartTypeServiceName.isEmpty() && xDoc.is())
    {
        uno::Reference< lang::XMultiServiceFactory > xFact( xDoc, uno::UNO_QUERY );
        if( xFact.is())
        {
            uno::Reference< chart::XDiagram > xDia( xFact->createInstance( rChartTypeServiceName ), uno::UNO_QUERY );
            if( xDia.is())
                xDoc->setDiagram( xDia );
        }
    }
}

SchXMLTitleContext::SchXMLTitleContext( SchXMLImportHelper& rImpHelper, SvXMLImport& rImport,
                                        OUString& rTitle,
                                        uno::Reference< drawing::XShape > xTitleShape ) :
        SvXMLImportContext( rImport ),
        mrImportHelper( rImpHelper ),
        mrTitle( rTitle ),
        mxTitleShape(std::move( xTitleShape ))
{
}

SchXMLTitleContext::~SchXMLTitleContext()
{}

void SchXMLTitleContext::startFastElement( sal_Int32 /*nElement*/,
    const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
{
    css::awt::Point aPosition;
    bool bHasXPosition=false;
    bool bHasYPosition=false;

    for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
    {
        switch (aIter.getToken())
        {
            case XML_ELEMENT(SVG, XML_X):
            case XML_ELEMENT(SVG_COMPAT, XML_X):
            {
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        aPosition.X, aIter.toView() );
                bHasXPosition = true;
                break;
            }
            case XML_ELEMENT(SVG, XML_Y):
            case XML_ELEMENT(SVG_COMPAT, XML_Y):
            {
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        aPosition.Y, aIter.toView() );
                bHasYPosition = true;
                break;
            }
            case XML_ELEMENT(CHART, XML_STYLE_NAME):
                msAutoStyleName = aIter.toString();
                break;
            default:
                XMLOFF_WARN_UNKNOWN("xmloff", aIter);
        }
    }

    if( mxTitleShape.is())
    {
        if( bHasXPosition && bHasYPosition )
            mxTitleShape->setPosition( aPosition );

        uno::Reference<beans::XPropertySet> xProp(mxTitleShape, uno::UNO_QUERY);
        mrImportHelper.FillAutoStyle(msAutoStyleName, xProp);
    }
}

css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLTitleContext::createFastChildContext(
    sal_Int32 nElement,
    const css::uno::Reference< css::xml::sax::XFastAttributeList >& /*xAttrList*/ )
{
    SvXMLImportContext* pContext = nullptr;

    if( nElement == XML_ELEMENT(TEXT, XML_P) ||
        nElement == XML_ELEMENT(LO_EXT, XML_P) )
    {
        pContext = new SchXMLParagraphContext( GetImport(), mrTitle );
    }
    else
        XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);

    return pContext;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
