/* -*- 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 <memory>
#include <sal/config.h>
#include <sal/log.hxx>
 
#include <algorithm>
#include <utility>
 
#include <chart2uno.hxx>
#include <miscuno.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <formulacell.hxx>
#include <unonames.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <rangeutl.hxx>
#include <hints.hxx>
#include <unoreflist.hxx>
#include <compiler.hxx>
#include <reftokenhelper.hxx>
#include <chartlis.hxx>
#include <tokenuno.hxx>
#include <cellvalue.hxx>
#include <tokenarray.hxx>
#include <scmatrix.hxx>
#include <brdcst.hxx>
#include <mtvelements.hxx>
 
#include <formula/opcode.hxx>
#include <o3tl/safeint.hxx>
#include <svl/numformat.hxx>
#include <svl/sharedstring.hxx>
 
#include <vcl/svapp.hxx>
 
#include <com/sun/star/beans/UnknownPropertyException.hpp>
#include <com/sun/star/chart/ChartDataRowSource.hpp>
#include <com/sun/star/chart2/data/LabeledDataSequence.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <comphelper/extract.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
 
#include <limits>
 
SC_SIMPLE_SERVICE_INFO( ScChart2DataProvider, u"ScChart2DataProvider"_ustr,
        u"com.sun.star.chart2.data.DataProvider"_ustr)
SC_SIMPLE_SERVICE_INFO( ScChart2DataSource, u"ScChart2DataSource"_ustr,
        u"com.sun.star.chart2.data.DataSource"_ustr)
SC_SIMPLE_SERVICE_INFO( ScChart2DataSequence, u"ScChart2DataSequence"_ustr,
        u"com.sun.star.chart2.data.DataSequence"_ustr)
 
using namespace ::com::sun::star;
using namespace ::formula;
using ::com::sun::star::uno::Sequence;
using ::std::unique_ptr;
using ::std::vector;
using ::std::distance;
using ::std::shared_ptr;
 
namespace
{
std::span<const SfxItemPropertyMapEntry> lcl_GetDataProviderPropertyMap()
{
    static const SfxItemPropertyMapEntry aDataProviderPropertyMap_Impl[] =
    {
        { SC_UNONAME_INCLUDEHIDDENCELLS, 0, cppu::UnoType<bool>::get(), 0, 0 },
        { SC_UNONAME_USE_INTERNAL_DATA_PROVIDER, 0, cppu::UnoType<bool>::get(), 0, 0 },
    };
    return aDataProviderPropertyMap_Impl;
}
 
std::span<const SfxItemPropertyMapEntry> lcl_GetDataSequencePropertyMap()
{
    static const SfxItemPropertyMapEntry aDataSequencePropertyMap_Impl[] =
    {
        { SC_UNONAME_HIDDENVALUES, 0, cppu::UnoType<uno::Sequence<sal_Int32>>::get(),                 0, 0 },
        { SC_UNONAME_ROLE, 0, cppu::UnoType<css::chart2::data::DataSequenceRole>::get(),                  0, 0 },
        { SC_UNONAME_INCLUDEHIDDENCELLS, 0,        cppu::UnoType<bool>::get(),                  0, 0 },
    };
    return aDataSequencePropertyMap_Impl;
}
 
struct lcl_appendTableNumber
{
    explicit lcl_appendTableNumber( OUStringBuffer & rBuffer ) :
            m_rBuffer( rBuffer )
    {}
    void operator() ( SCTAB nTab )
    {
        // there is no append with SCTAB or sal_Int16
        m_rBuffer.append( static_cast< sal_Int32 >( nTab ));
        m_rBuffer.append( ' ' );
    }
private:
    OUStringBuffer & m_rBuffer;
};
 
OUString lcl_createTableNumberList( const ::std::vector< SCTAB > & rTableVector )
{
    OUStringBuffer aBuffer;
    ::std::for_each( rTableVector.begin(), rTableVector.end(), lcl_appendTableNumber( aBuffer ));
    // remove last trailing ' '
    if( !aBuffer.isEmpty() )
        aBuffer.setLength( aBuffer.getLength() - 1 );
    return aBuffer.makeStringAndClear();
}
 
uno::Reference< frame::XModel > lcl_GetXModel( const ScDocument * pDoc )
{
    uno::Reference< frame::XModel > xModel;
    ScDocShell * pObjSh( pDoc ? pDoc->GetDocumentShell() : nullptr );
    if( pObjSh )
        xModel.set( pObjSh->GetModel());
    return xModel;
}
 
struct TokenTable
{
    SCROW mnRowCount;
    SCCOL mnColCount;
    vector<std::unique_ptr<FormulaToken>> maTokens;
 
    // noncopyable
    TokenTable(const TokenTable&) = delete;
    const TokenTable& operator=(const TokenTable&) = delete;
 
    TokenTable()
        : mnRowCount(0)
        , mnColCount(0)
    {
    }
 
    void init( SCCOL nColCount, SCROW nRowCount )
    {
        mnColCount = nColCount;
        mnRowCount = nRowCount;
        maTokens.reserve(mnColCount*mnRowCount);
    }
    void clear()
    {
        for (auto & rToken : maTokens)
            rToken.reset();
    }
 
    void push_back( std::unique_ptr<FormulaToken> pToken )
    {
        maTokens.push_back( std::move(pToken) );
        OSL_ENSURE( maTokens.size()<= o3tl::make_unsigned( mnColCount*mnRowCount ), "too many tokens" );
    }
 
    sal_uInt32 getIndex(SCCOL nCol, SCROW nRow) const
    {
        OSL_ENSURE( nCol<mnColCount, "wrong column index" );
        OSL_ENSURE( nRow<mnRowCount, "wrong row index" );
        sal_uInt32 nRet = static_cast<sal_uInt32>(nCol*mnRowCount + nRow);
        OSL_ENSURE( maTokens.size()>= o3tl::make_unsigned( mnColCount*mnRowCount ), "too few tokens" );
        return nRet;
    }
 
    vector<ScTokenRef> getColRanges(const ScDocument* pDoc, SCCOL nCol) const;
    vector<ScTokenRef> getRowRanges(const ScDocument* pDoc, SCROW nRow) const;
    vector<ScTokenRef> getAllRanges(const ScDocument* pDoc) const;
};
 
vector<ScTokenRef> TokenTable::getColRanges(const ScDocument* pDoc, SCCOL nCol) const
{
    if (nCol >= mnColCount)
        return vector<ScTokenRef>();
    if( mnRowCount<=0 )
        return vector<ScTokenRef>();
 
    vector<ScTokenRef> aTokens;
    sal_uInt32 nLast = getIndex(nCol, mnRowCount-1);
    for (sal_uInt32 i = getIndex(nCol, 0); i <= nLast; ++i)
    {
        FormulaToken* p = maTokens[i].get();
        if (!p)
            continue;
 
        ScTokenRef pCopy(p->Clone());
        ScRefTokenHelper::join(pDoc, aTokens, pCopy, ScAddress());
    }
    return aTokens;
}
 
vector<ScTokenRef> TokenTable::getRowRanges(const ScDocument* pDoc, SCROW nRow) const
{
    if (nRow >= mnRowCount)
        return vector<ScTokenRef>();
    if( mnColCount<=0 )
        return vector<ScTokenRef>();
 
    vector<ScTokenRef> aTokens;
    sal_uInt32 nLast = getIndex(mnColCount-1, nRow);
    for (sal_uInt32 i = getIndex(0, nRow); i <= nLast; i += mnRowCount)
    {
        FormulaToken* p = maTokens[i].get();
        if (!p)
            continue;
 
        ScTokenRef p2(p->Clone());
        ScRefTokenHelper::join(pDoc, aTokens, p2, ScAddress());
    }
    return aTokens;
}
 
vector<ScTokenRef> TokenTable::getAllRanges(const ScDocument* pDoc) const
{
    vector<ScTokenRef> aTokens;
    sal_uInt32 nStop = mnColCount*mnRowCount;
    for (sal_uInt32 i = 0; i < nStop; i++)
    {
        FormulaToken* p = maTokens[i].get();
        if (!p)
            continue;
 
        ScTokenRef p2(p->Clone());
        ScRefTokenHelper::join(pDoc, aTokens, p2, ScAddress());
    }
    return aTokens;
}
 
typedef std::map<SCROW, std::unique_ptr<FormulaToken>> FormulaTokenMap;
typedef std::map<sal_uInt32, FormulaTokenMap> FormulaTokenMapMap;
 
class Chart2PositionMap
{
public:
    Chart2PositionMap(SCCOL nColCount, SCROW nRowCount,
                      bool bFillRowHeader, bool bFillColumnHeader, FormulaTokenMapMap& rCols,
                      ScDocument* pDoc );
    ~Chart2PositionMap();
 
    SCCOL getDataColCount() const { return mnDataColCount; }
    SCROW getDataRowCount() const { return mnDataRowCount; }
 
    vector<ScTokenRef> getLeftUpperCornerRanges() const;
    vector<ScTokenRef> getAllColHeaderRanges() const;
    vector<ScTokenRef> getAllRowHeaderRanges() const;
 
    vector<ScTokenRef> getColHeaderRanges(SCCOL nChartCol) const;
    vector<ScTokenRef> getRowHeaderRanges(SCROW nChartRow) const;
 
    vector<ScTokenRef> getDataColRanges(SCCOL nCol) const;
    vector<ScTokenRef> getDataRowRanges(SCROW nRow) const;
 
private:
    const ScDocument* mpDoc;
    SCCOL mnDataColCount;
    SCROW mnDataRowCount;
 
    TokenTable maLeftUpperCorner; //nHeaderColCount*nHeaderRowCount
    TokenTable maColHeaders; //mnDataColCount*nHeaderRowCount
    TokenTable maRowHeaders; //nHeaderColCount*mnDataRowCount
    TokenTable maData;//mnDataColCount*mnDataRowCount
};
 
Chart2PositionMap::Chart2PositionMap(SCCOL nAllColCount,  SCROW nAllRowCount,
                                     bool bFillRowHeader, bool bFillColumnHeader, FormulaTokenMapMap& rCols, ScDocument* pDoc)
{
    mpDoc = pDoc;
    // if bFillRowHeader is true, at least the first column serves as a row header.
    //  If more than one column is pure text all the first pure text columns are used as header.
    // Likewise, if bFillColumnHeader is true, at least the first row serves as a column header.
    //  If more than one row is pure text all the first pure text rows are used as header.
 
    SCROW nHeaderRowCount = (bFillColumnHeader && nAllColCount && nAllRowCount) ? 1 : 0;
    SCCOL nHeaderColCount = (bFillRowHeader && nAllColCount && nAllRowCount) ? 1 : 0;
 
    if( pDoc && (nHeaderColCount || nHeaderRowCount ) )
    {
        //check whether there is more than one text column or row that should be added to the headers
        SCROW nMaxHeaderRow = nAllRowCount;
        SCCOL nCol = 0;
        for (auto it = rCols.begin(); it != rCols.end(); ++it, ++nCol)
        {
            // Skip header columns
            if (nCol < nHeaderColCount)
                continue;
 
            const auto& rCol = *it;
 
            bool bFoundValuesInCol = false;
            bool bFoundAnythingInCol = false;
            SCROW nRow = 0;
            for (auto it2 = rCol.second.begin(); it2 != rCol.second.end(); ++it2, ++nRow)
            {
                const auto& rCell = *it2;
 
                // Skip header rows
                if (nRow < nHeaderRowCount || !rCell.second)
                    continue;
 
                ScRange aRange;
                bool bExternal = false;
                StackVar eType = rCell.second->GetType();
                if( eType==svExternal || eType==svExternalSingleRef || eType==svExternalDoubleRef || eType==svExternalName )
                    bExternal = true;//lllll todo correct?
                ScTokenRef pSharedToken(rCell.second->Clone());
                ScRefTokenHelper::getRangeFromToken(pDoc, aRange, pSharedToken, ScAddress(), bExternal);
                SCCOL nCol1=0, nCol2=0;
                SCROW nRow1=0, nRow2=0;
                SCTAB nTab1=0, nTab2=0;
                aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
                if ( pDoc->HasValueData( nCol1, nRow1, nTab1 ) )
                {
                    // Found some numeric data
                    bFoundValuesInCol = true;
                    nMaxHeaderRow = std::min(nMaxHeaderRow, nRow);
                    break;
                }
                if ( pDoc->HasData( nCol1, nRow1, nTab1 ) )
                {
                    // Found some other data (non-numeric)
                    bFoundAnythingInCol = true;
                }
                else
                {
                    // If cell is empty, it belongs to data
                    nMaxHeaderRow = std::min(nMaxHeaderRow, nRow);
                }
            }
 
            if (nHeaderColCount && !bFoundValuesInCol && bFoundAnythingInCol && nCol == nHeaderColCount)
            {
                // There is no values in row, but some data. And this column is next to header
                // So let's put it to header
                nHeaderColCount++;
            }
        }
 
        if (nHeaderRowCount)
        {
            nHeaderRowCount = nMaxHeaderRow;
        }
    }
 
    mnDataColCount = nAllColCount - nHeaderColCount;
    mnDataRowCount = nAllRowCount - nHeaderRowCount;
 
    maLeftUpperCorner.init(nHeaderColCount,nHeaderRowCount);
    maColHeaders.init(mnDataColCount,nHeaderRowCount);
    maRowHeaders.init(nHeaderColCount,mnDataRowCount);
    maData.init(mnDataColCount,mnDataRowCount);
 
    FormulaTokenMapMap::iterator it1 = rCols.begin();
    for (SCCOL nCol = 0; nCol < nAllColCount; ++nCol)
    {
        if (it1 != rCols.end())
        {
            FormulaTokenMap& rCol = it1->second;
            FormulaTokenMap::iterator it2 = rCol.begin();
            for (SCROW nRow = 0; nRow < nAllRowCount; ++nRow)
            {
                std::unique_ptr<FormulaToken> pToken;
                if (it2 != rCol.end())
                {
                    pToken = std::move(it2->second);
                    ++it2;
                }
 
                if( nCol < nHeaderColCount )
                {
                    if( nRow < nHeaderRowCount )
                        maLeftUpperCorner.push_back(std::move(pToken));
                    else
                        maRowHeaders.push_back(std::move(pToken));
                }
                else if( nRow < nHeaderRowCount )
                    maColHeaders.push_back(std::move(pToken));
                else
                    maData.push_back(std::move(pToken));
            }
            ++it1;
        }
    }
}
 
Chart2PositionMap::~Chart2PositionMap()
{
    maLeftUpperCorner.clear();
    maColHeaders.clear();
    maRowHeaders.clear();
    maData.clear();
}
 
vector<ScTokenRef> Chart2PositionMap::getLeftUpperCornerRanges() const
{
    return maLeftUpperCorner.getAllRanges(mpDoc);
}
vector<ScTokenRef> Chart2PositionMap::getAllColHeaderRanges() const
{
    return maColHeaders.getAllRanges(mpDoc);
}
vector<ScTokenRef> Chart2PositionMap::getAllRowHeaderRanges() const
{
    return maRowHeaders.getAllRanges(mpDoc);
}
vector<ScTokenRef> Chart2PositionMap::getColHeaderRanges(SCCOL nCol) const
{
    return maColHeaders.getColRanges(mpDoc, nCol);
}
vector<ScTokenRef> Chart2PositionMap::getRowHeaderRanges(SCROW nRow) const
{
    return maRowHeaders.getRowRanges(mpDoc, nRow);
}
 
vector<ScTokenRef> Chart2PositionMap::getDataColRanges(SCCOL nCol) const
{
    return maData.getColRanges(mpDoc, nCol);
}
 
vector<ScTokenRef> Chart2PositionMap::getDataRowRanges(SCROW nRow) const
{
    return maData.getRowRanges(mpDoc, nRow);
}
 
/**
 * Designed to be a drop-in replacement for ScChartPositioner, in order to
 * handle external references.
 */
class Chart2Positioner
{
    enum GlueType
    {
        GLUETYPE_NA,
        GLUETYPE_NONE,
        GLUETYPE_COLS,
        GLUETYPE_ROWS,
        GLUETYPE_BOTH
    };
 
public:
    Chart2Positioner(const Chart2Positioner&) = delete;
    const Chart2Positioner& operator=(const Chart2Positioner&) = delete;
 
    Chart2Positioner(ScDocument* pDoc, const vector<ScTokenRef>& rRefTokens) :
        mrRefTokens(rRefTokens),
        meGlue(GLUETYPE_NA),
        mnStartCol(0),
        mnStartRow(0),
        mpDoc(pDoc),
        mbColHeaders(false),
        mbRowHeaders(false),
        mbDummyUpperLeft(false)
    {
    }
 
    void setHeaders(bool bColHeaders, bool bRowHeaders)
    {
        mbColHeaders = bColHeaders;
        mbRowHeaders = bRowHeaders;
    }
 
    Chart2PositionMap* getPositionMap()
    {
        createPositionMap();
        return mpPositionMap.get();
    }
 
private:
    void invalidateGlue();
    void glueState();
    void calcGlueState(SCCOL nCols, SCROW nRows);
    void createPositionMap();
 
private:
    const vector<ScTokenRef>& mrRefTokens;
    std::unique_ptr<Chart2PositionMap> mpPositionMap;
    GlueType    meGlue;
    SCCOL       mnStartCol;
    SCROW       mnStartRow;
    ScDocument* mpDoc;
    bool mbColHeaders:1;
    bool mbRowHeaders:1;
    bool mbDummyUpperLeft:1;
};
 
void Chart2Positioner::invalidateGlue()
{
    meGlue = GLUETYPE_NA;
    mpPositionMap.reset();
}
 
void Chart2Positioner::glueState()
{
    if (meGlue != GLUETYPE_NA)
        return;
 
    mbDummyUpperLeft = false;
    if (mrRefTokens.size() <= 1)
    {
        // Source data consists of only one data range.
        const ScTokenRef& p = mrRefTokens.front();
        ScComplexRefData aData;
        if (ScRefTokenHelper::getDoubleRefDataFromToken(aData, p))
        {
            if (aData.Ref1.Tab() == aData.Ref2.Tab())
                meGlue = GLUETYPE_NONE;
            else
                meGlue = GLUETYPE_COLS;
            mnStartCol = aData.Ref1.Col();
            mnStartRow = aData.Ref1.Row();
        }
        else
        {
            invalidateGlue();
            mnStartCol = 0;
            mnStartRow = 0;
        }
        return;
    }
 
    ScComplexRefData aData;
    if (!ScRefTokenHelper::getDoubleRefDataFromToken(aData, mrRefTokens.front()))
    {
        SAL_WARN("sc", "Chart2Positioner::glueState getDoubleRefDataFromToken failed");
        invalidateGlue();
        mnStartCol = 0;
        mnStartRow = 0;
        return;
    }
    mnStartCol = aData.Ref1.Col();
    mnStartRow = aData.Ref1.Row();
 
    SCCOL nEndCol = 0;
    SCROW nEndRow = 0;
    for (const auto& rxToken : mrRefTokens)
    {
        ScRefTokenHelper::getDoubleRefDataFromToken(aData, rxToken);
        SCCOLROW n1 = aData.Ref1.Col();
        SCCOLROW n2 = aData.Ref2.Col();
        if (n1 > mpDoc->MaxCol())
            n1 = mpDoc->MaxCol();
        if (n2 > mpDoc->MaxCol())
            n2 = mpDoc->MaxCol();
        if (n1 < mnStartCol)
            mnStartCol = static_cast<SCCOL>(n1);
        if (n2 > nEndCol)
            nEndCol = static_cast<SCCOL>(n2);
 
        n1 = aData.Ref1.Row();
        n2 = aData.Ref2.Row();
        if (n1 > mpDoc->MaxRow())
            n1 = mpDoc->MaxRow();
        if (n2 > mpDoc->MaxRow())
            n2 = mpDoc->MaxRow();
 
        if (n1 < mnStartRow)
            mnStartRow = static_cast<SCROW>(n1);
        if (n2 > nEndRow)
            nEndRow = static_cast<SCROW>(n2);
    }
 
    if (mnStartCol == nEndCol)
    {
        // All source data is in a single column.
        meGlue = GLUETYPE_ROWS;
        return;
    }
 
    if (mnStartRow == nEndRow)
    {
        // All source data is in a single row.
        meGlue = GLUETYPE_COLS;
        return;
    }
 
    // total column size
    SCCOL nC = nEndCol - mnStartCol + 1;
 
    // total row size
    SCROW nR = nEndRow - mnStartRow + 1;
 
    // #i103540# prevent invalid vector size
    if ((nC <= 0) || (nR <= 0))
    {
        invalidateGlue();
        mnStartCol = 0;
        mnStartRow = 0;
        return;
    }
 
    calcGlueState(nC, nR);
}
 
enum State { Hole = 0, Occupied = 1, Free = 2, Glue = 3 };
 
void Chart2Positioner::calcGlueState(SCCOL nColSize, SCROW nRowSize)
{
    // TODO: This code can use some space optimization.  Using an array to
    // store individual cell's states is terribly inefficient esp for large
    // data ranges; let's use flat_segment_tree to reduce memory usage here.
 
    sal_uInt32 nCR = static_cast<sal_uInt32>(nColSize*nRowSize);
 
    vector<State> aCellStates(nCR, Hole);
 
    // Mark all referenced cells "occupied".
    for (const auto& rxToken : mrRefTokens)
    {
        ScComplexRefData aData;
        ScRefTokenHelper::getDoubleRefDataFromToken(aData, rxToken);
        auto nCol1 = aData.Ref1.Col() - mnStartCol;
        auto nCol2 = aData.Ref2.Col() - mnStartCol;
        SCROW nRow1 = aData.Ref1.Row() - mnStartRow;
        SCROW nRow2 = aData.Ref2.Row() - mnStartRow;
        for (SCCOLROW nCol = nCol1; nCol <= nCol2 && nCol >= 0; ++nCol)
            for (SCCOLROW nRow = nRow1; nRow <= nRow2 && nRow >= 0; ++nRow)
            {
                size_t i = nCol*nRowSize + nRow;
                aCellStates[i] = Occupied;
            }
    }
 
    // If at least one cell in either the first column or first row is empty,
    // we don't glue at all unless the whole column or row is empty; we expect
    // all cells in the first column / row to be fully populated.  If we have
    // empty column or row, then we do glue by the column or row,
    // respectively.
 
    bool bGlue = true;
    bool bGlueCols = false;
    for (auto nCol = 0; bGlue && nCol < nColSize; ++nCol)
    {
        for (SCROW nRow = 0; bGlue && nRow < nRowSize; ++nRow)
        {
            size_t i = nCol*nRowSize + nRow;
            if (aCellStates[i] == Occupied)
            {
                if (nCol == 0 || nRow == 0)
                    break;
 
                bGlue = false;
            }
            else
                aCellStates[i] = Free;
        }
        size_t nLast = (nCol+1)*nRowSize - 1; // index for the last cell in the column.
        if (bGlue && aCellStates[nLast] == Free)
        {
            // Whole column is empty.
            aCellStates[nLast] = Glue;
            bGlueCols = true;
        }
    }
 
    bool bGlueRows = false;
    for (SCROW nRow = 0; bGlue && nRow < nRowSize; ++nRow)
    {
        size_t i = nRow;
        for (SCCOL nCol = 0; bGlue && nCol < nColSize; ++nCol, i += nRowSize)
        {
            if (aCellStates[i] == Occupied)
            {
                if (nCol == 0 || nRow == 0)
                    break;
 
                bGlue = false;
            }
            else
                aCellStates[i] = Free;
        }
        i = (nColSize-1)*nRowSize + nRow; // index for the row position in the last column.
        if (bGlue && aCellStates[i] == Free)
        {
            // Whole row is empty.
            aCellStates[i] = Glue;
            bGlueRows = true;
        }
    }
 
    size_t i = 1;
    for (sal_uInt32 n = 1; bGlue && n < nCR; ++n, ++i)
        if (aCellStates[i] == Hole)
            bGlue = false;
 
    if (bGlue)
    {
        if (bGlueCols && bGlueRows)
            meGlue = GLUETYPE_BOTH;
        else if (bGlueRows)
            meGlue = GLUETYPE_ROWS;
        else
            meGlue = GLUETYPE_COLS;
        if (aCellStates.front() != Occupied)
            mbDummyUpperLeft = true;
    }
    else
        meGlue = GLUETYPE_NONE;
}
 
void Chart2Positioner::createPositionMap()
{
    if (meGlue == GLUETYPE_NA && mpPositionMap)
        mpPositionMap.reset();
 
    if (mpPositionMap)
        return;
 
    glueState();
 
    bool bNoGlue = (meGlue == GLUETYPE_NONE);
    FormulaTokenMapMap aCols;
    SCROW nNoGlueRow = 0;
    for (const ScTokenRef& pToken : mrRefTokens)
    {
        bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
        sal_uInt16 nFileId = bExternal ? pToken->GetIndex() : 0;
        svl::SharedString aTabName = svl::SharedString::getEmptyString();
        if (bExternal)
            aTabName = pToken->GetString();
 
        ScComplexRefData aData;
        if( !ScRefTokenHelper::getDoubleRefDataFromToken(aData, pToken) )
            break;
        const ScSingleRefData& s = aData.Ref1;
        const ScSingleRefData& e = aData.Ref2;
        SCCOL nCol1 = s.Col(), nCol2 = e.Col();
        SCROW nRow1 = s.Row(), nRow2 = e.Row();
        SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
        assert(nTab2 < MAXTABCOUNT);
 
        for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
        {
            // columns on secondary sheets are appended; we treat them as if
            // all columns are on the same sheet.  TODO: We can't assume that
            // the column range is 16-bit; remove that restriction.
            sal_uInt32 nInsCol = (static_cast<sal_uInt32>(nTab) << 16) |
                (bNoGlue ? 0 : static_cast<sal_uInt32>(nCol1));
 
            for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol, ++nInsCol)
            {
                FormulaTokenMap& rCol = aCols[nInsCol];
 
                auto nInsRow = bNoGlue ? nNoGlueRow : nRow1;
                for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow, ++nInsRow)
                {
                    ScSingleRefData aCellData;
                    aCellData.InitFlags();
                    aCellData.SetFlag3D(true);
                    aCellData.SetColRel(false);
                    aCellData.SetRowRel(false);
                    aCellData.SetTabRel(false);
                    aCellData.SetAbsCol(nCol);
                    aCellData.SetAbsRow(nRow);
                    aCellData.SetAbsTab(nTab);
 
                    auto& rCell = rCol[nInsRow];
                    if (!rCell)
                    {
                        if (bExternal)
                            rCell.reset(new ScExternalSingleRefToken(nFileId, aTabName, aCellData));
                        else
                            rCell.reset(new ScSingleRefToken(mpDoc->GetSheetLimits(), aCellData));
                    }
                }
            }
        }
        nNoGlueRow += nRow2 - nRow1 + 1;
    }
 
    bool bFillRowHeader = mbRowHeaders;
    bool bFillColumnHeader = mbColHeaders;
 
    SCSIZE nAllColCount = static_cast<SCSIZE>(aCols.size());
    SCSIZE nAllRowCount = 0;
    if (!aCols.empty())
    {
        FormulaTokenMap& rCol = aCols.begin()->second;
        if (mbDummyUpperLeft)
            rCol.try_emplace( 0, nullptr );        // dummy for labeling
        nAllRowCount = static_cast<SCSIZE>(rCol.size());
    }
 
    if( nAllColCount!=0 && nAllRowCount!=0 )
    {
        if (bNoGlue)
        {
            FormulaTokenMap& rFirstCol = aCols.begin()->second;
            for (const auto& rFirstColEntry : rFirstCol)
            {
                SCROW nKey = rFirstColEntry.first;
                for (auto& rEntry : aCols)
                {
                    FormulaTokenMap& rCol = rEntry.second;
                    rCol.try_emplace( nKey, nullptr );
                }
            }
        }
    }
    mpPositionMap.reset(
        new Chart2PositionMap(
            static_cast<SCCOL>(nAllColCount), static_cast<SCROW>(nAllRowCount),
            bFillRowHeader, bFillColumnHeader, aCols, mpDoc));
}
 
/**
 * Function object to create a range string from a token list.
 */
class Tokens2RangeString
{
public:
    Tokens2RangeString(ScDocument& rDoc, FormulaGrammar::Grammar eGram, sal_Unicode cRangeSep) :
        mpRangeStr(std::make_shared<OUStringBuffer>()),
        mpDoc(&rDoc),
        meGrammar(eGram),
        mcRangeSep(cRangeSep),
        mbFirst(true)
    {
    }
 
    void operator() (const ScTokenRef& rToken)
    {
        ScCompiler aCompiler(*mpDoc, ScAddress(0,0,0), meGrammar);
        OUString aStr;
        aCompiler.CreateStringFromToken(aStr, rToken.get());
        if (mbFirst)
            mbFirst = false;
        else
            mpRangeStr->append(mcRangeSep);
        mpRangeStr->append(aStr);
    }
 
    void getString(OUString& rStr)
    {
        rStr = mpRangeStr->makeStringAndClear();
    }
 
private:
    shared_ptr<OUStringBuffer>  mpRangeStr;
    ScDocument*         mpDoc;
    FormulaGrammar::Grammar  meGrammar;
    sal_Unicode         mcRangeSep;
    bool                mbFirst;
};
 
/**
 * Function object to convert a list of tokens into a string form suitable
 * for ODF export.  In ODF, a range is expressed as
 *
 *   (start cell address):(end cell address)
 *
 * and each address doesn't include any '$' symbols.
 */
class Tokens2RangeStringXML
{
public:
    explicit Tokens2RangeStringXML(ScDocument& rDoc) :
        mpRangeStr(std::make_shared<OUStringBuffer>()),
        mpDoc(&rDoc),
        mbFirst(true)
    {
    }
 
    void operator() (const ScTokenRef& rToken)
    {
        if (mbFirst)
            mbFirst = false;
        else
            mpRangeStr->append(mcRangeSep);
 
        ScTokenRef aStart, aEnd;
        bool bValidToken = splitRangeToken(*mpDoc, rToken, aStart, aEnd);
        // Check there is a valid reference in named range
        if (!bValidToken && rToken->GetType() == svIndex && rToken->GetOpCode() == ocName)
        {
            ScRangeData* pNameRange = mpDoc->FindRangeNameBySheetAndIndex(rToken->GetSheet(), rToken->GetIndex());
            if (pNameRange->HasReferences())
            {
                const ScTokenRef aTempToken = pNameRange->GetCode()->FirstToken();
                bValidToken = splitRangeToken(*mpDoc, aTempToken, aStart, aEnd);
            }
        }
 
        OSL_ENSURE(bValidToken, "invalid token");
        if (!bValidToken)
            return;
 
        ScCompiler aCompiler(*mpDoc, ScAddress(0,0,0), FormulaGrammar::GRAM_ENGLISH);
        {
            OUString aStr;
            aCompiler.CreateStringFromToken(aStr, aStart.get());
            mpRangeStr->append(aStr);
        }
        mpRangeStr->append(mcAddrSep);
        {
            OUString aStr;
            aCompiler.CreateStringFromToken(aStr, aEnd.get());
            mpRangeStr->append(aStr);
        }
    }
 
    void getString(OUString& rStr)
    {
        rStr = mpRangeStr->makeStringAndClear();
    }
 
private:
    static bool splitRangeToken(const ScDocument& rDoc, const ScTokenRef& pToken, ScTokenRef& rStart, ScTokenRef& rEnd)
    {
        ScComplexRefData aData;
        bool bIsRefToken = ScRefTokenHelper::getDoubleRefDataFromToken(aData, pToken);
        OSL_ENSURE(bIsRefToken, "invalid token");
        if (!bIsRefToken)
            return false;
        bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
        sal_uInt16 nFileId = bExternal ? pToken->GetIndex() : 0;
        const svl::SharedString aTabName = bExternal ? pToken->GetString() : svl::SharedString::getEmptyString();
 
        // In saving to XML, we don't prepend address with '$'.
        setRelative(aData.Ref1);
        setRelative(aData.Ref2);
 
        // In XML, the range must explicitly specify sheet name.
        aData.Ref1.SetFlag3D(true);
        aData.Ref2.SetFlag3D(true);
 
        if (bExternal)
            rStart.reset(new ScExternalSingleRefToken(nFileId, aTabName, aData.Ref1));
        else
            rStart.reset(new ScSingleRefToken(rDoc.GetSheetLimits(), aData.Ref1));
 
        if (bExternal)
            rEnd.reset(new ScExternalSingleRefToken(nFileId, aTabName, aData.Ref2));
        else
            rEnd.reset(new ScSingleRefToken(rDoc.GetSheetLimits(), aData.Ref2));
        return true;
    }
 
    static void setRelative(ScSingleRefData& rData)
    {
        rData.SetColRel(true);
        rData.SetRowRel(true);
        rData.SetTabRel(true);
    }
 
private:
    shared_ptr<OUStringBuffer>  mpRangeStr;
    ScDocument*                 mpDoc;
    static const sal_Unicode    mcRangeSep = ' ';
    static const sal_Unicode    mcAddrSep = ':';
    bool                        mbFirst;
};
 
void lcl_convertTokensToString(OUString& rStr, const vector<ScTokenRef>& rTokens, ScDocument& rDoc)
{
    const sal_Unicode cRangeSep = ScCompiler::GetNativeSymbolChar(ocSep);
    FormulaGrammar::Grammar eGrammar = rDoc.GetGrammar();
    Tokens2RangeString func(rDoc, eGrammar, cRangeSep);
    func = ::std::for_each(rTokens.begin(), rTokens.end(), func);
    func.getString(rStr);
}
 
} // anonymous namespace
 
// DataProvider ==============================================================
 
ScChart2DataProvider::ScChart2DataProvider( ScDocument* pDoc )
    : m_pDocument( pDoc)
    , m_aPropSet(lcl_GetDataProviderPropertyMap())
    , m_bIncludeHiddenCells( true)
{
    if ( m_pDocument )
        m_pDocument->AddUnoObject( *this);
}
 
ScChart2DataProvider::~ScChart2DataProvider()
{
    SolarMutexGuard g;
 
    if ( m_pDocument )
        m_pDocument->RemoveUnoObject( *this);
}
 
void ScChart2DataProvider::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
{
    if ( rHint.GetId() == SfxHintId::Dying )
    {
        m_pDocument = nullptr;
    }
}
 
sal_Bool SAL_CALL ScChart2DataProvider::createDataSourcePossible( const uno::Sequence< beans::PropertyValue >& aArguments )
{
    SolarMutexGuard aGuard;
    if( ! m_pDocument )
        return false;
 
    OUString aRangeRepresentation;
    for(const auto& rArgument : aArguments)
    {
        if ( rArgument.Name == "CellRangeRepresentation" )
        {
            rArgument.Value >>= aRangeRepresentation;
        }
    }
 
    vector<ScTokenRef> aTokens;
    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
    ScRefTokenHelper::compileRangeRepresentation(
        aTokens, aRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
    return !aTokens.empty();
}
 
namespace
{
 
uno::Reference< chart2::data::XLabeledDataSequence > lcl_createLabeledDataSequenceFromTokens(
    vector< ScTokenRef > && aValueTokens, vector< ScTokenRef > && aLabelTokens,
    ScDocument* pDoc, bool bIncludeHiddenCells )
{
    uno::Reference< chart2::data::XLabeledDataSequence >  xResult;
    bool bHasValues = !aValueTokens.empty();
    bool bHasLabel = !aLabelTokens.empty();
    if( bHasValues || bHasLabel )
    {
        try
        {
            const uno::Reference< uno::XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
            if ( xContext.is() )
            {
                xResult.set( chart2::data::LabeledDataSequence::create(xContext), uno::UNO_QUERY_THROW );
            }
            if ( bHasValues )
            {
                uno::Reference< chart2::data::XDataSequence > xSeq( new ScChart2DataSequence( pDoc, std::move(aValueTokens), bIncludeHiddenCells ) );
                xResult->setValues( xSeq );
            }
            if ( bHasLabel )
            {
                //Labels should always include hidden cells, regardless of the bIncludeHiddenCells setting
                uno::Reference< chart2::data::XDataSequence > xLabelSeq( new ScChart2DataSequence( pDoc, std::move(aLabelTokens), true ) );
                xResult->setLabel( xLabelSeq );
            }
        }
        catch( const uno::Exception& )
        {
        }
    }
    return xResult;
}
 
/**
 * Check the current list of reference tokens, and add the upper left
 * corner of the minimum range that encloses all ranges if certain
 * conditions are met.
 *
 * @param rRefTokens list of reference tokens
 *
 * @return true if the corner was added, false otherwise.
 */
bool lcl_addUpperLeftCornerIfMissing(const ScDocument* pDoc, vector<ScTokenRef>& rRefTokens,
            SCROW nCornerRowCount, SCCOL nCornerColumnCount)
{
    using ::std::max;
    using ::std::min;
 
    if (rRefTokens.empty())
        return false;
 
    SCCOL nMinCol = pDoc->GetSheetLimits().GetMaxColCount();
    SCROW nMinRow = pDoc->GetSheetLimits().GetMaxRowCount();
    SCCOL nMaxCol = 0;
    SCROW nMaxRow = 0;
    SCTAB nTab    = 0;
 
    sal_uInt16 nFileId = 0;
    svl::SharedString aExtTabName;
    bool bExternal = false;
 
    vector<ScTokenRef>::const_iterator itr = rRefTokens.begin(), itrEnd = rRefTokens.end();
 
    // Get the first ref token.
    ScTokenRef pToken = *itr;
    switch (pToken->GetType())
    {
        case svSingleRef:
        {
            const ScSingleRefData& rData = *pToken->GetSingleRef();
            nMinCol = rData.Col();
            nMinRow = rData.Row();
            nMaxCol = rData.Col();
            nMaxRow = rData.Row();
            nTab = rData.Tab();
        }
        break;
        case svDoubleRef:
        {
            const ScComplexRefData& rData = *pToken->GetDoubleRef();
            nMinCol = min(rData.Ref1.Col(), rData.Ref2.Col());
            nMinRow = min(rData.Ref1.Row(), rData.Ref2.Row());
            nMaxCol = max(rData.Ref1.Col(), rData.Ref2.Col());
            nMaxRow = max(rData.Ref1.Row(), rData.Ref2.Row());
            nTab = rData.Ref1.Tab();
        }
        break;
        case svExternalSingleRef:
        {
            const ScSingleRefData& rData = *pToken->GetSingleRef();
            nMinCol = rData.Col();
            nMinRow = rData.Row();
            nMaxCol = rData.Col();
            nMaxRow = rData.Row();
            nTab = rData.Tab();
            nFileId = pToken->GetIndex();
            aExtTabName = pToken->GetString();
            bExternal = true;
        }
        break;
        case svExternalDoubleRef:
        {
            const ScComplexRefData& rData = *pToken->GetDoubleRef();
            nMinCol = min(rData.Ref1.Col(), rData.Ref2.Col());
            nMinRow = min(rData.Ref1.Row(), rData.Ref2.Row());
            nMaxCol = max(rData.Ref1.Col(), rData.Ref2.Col());
            nMaxRow = max(rData.Ref1.Row(), rData.Ref2.Row());
            nTab = rData.Ref1.Tab();
            nFileId = pToken->GetIndex();
            aExtTabName = pToken->GetString();
            bExternal = true;
        }
        break;
        default:
            ;
    }
 
    // Determine the minimum range enclosing all data ranges.  Also make sure
    // that they are all on the same table.
 
    for (++itr; itr != itrEnd; ++itr)
    {
        pToken = *itr;
        switch (pToken->GetType())
        {
            case svSingleRef:
            {
                const ScSingleRefData& rData = *pToken->GetSingleRef();
 
                nMinCol = min(nMinCol, rData.Col());
                nMinRow = min(nMinRow, rData.Row());
                nMaxCol = max(nMaxCol, rData.Col());
                nMaxRow = max(nMaxRow, rData.Row());
                if (nTab != rData.Tab() || bExternal)
                    return false;
            }
            break;
            case svDoubleRef:
            {
                const ScComplexRefData& rData = *pToken->GetDoubleRef();
 
                nMinCol = min(nMinCol, rData.Ref1.Col());
                nMinCol = min(nMinCol, rData.Ref2.Col());
                nMinRow = min(nMinRow, rData.Ref1.Row());
                nMinRow = min(nMinRow, rData.Ref2.Row());
 
                nMaxCol = max(nMaxCol, rData.Ref1.Col());
                nMaxCol = max(nMaxCol, rData.Ref2.Col());
                nMaxRow = max(nMaxRow, rData.Ref1.Row());
                nMaxRow = max(nMaxRow, rData.Ref2.Row());
 
                if (nTab != rData.Ref1.Tab() || bExternal)
                    return false;
            }
            break;
            case svExternalSingleRef:
            {
                if (!bExternal)
                    return false;
 
                if (nFileId != pToken->GetIndex() || aExtTabName != pToken->GetString())
                    return false;
 
                const ScSingleRefData& rData = *pToken->GetSingleRef();
 
                nMinCol = min(nMinCol, rData.Col());
                nMinRow = min(nMinRow, rData.Row());
                nMaxCol = max(nMaxCol, rData.Col());
                nMaxRow = max(nMaxRow, rData.Row());
            }
            break;
            case svExternalDoubleRef:
            {
                if (!bExternal)
                    return false;
 
                if (nFileId != pToken->GetIndex() || aExtTabName != pToken->GetString())
                    return false;
 
                const ScComplexRefData& rData = *pToken->GetDoubleRef();
 
                nMinCol = min(nMinCol, rData.Ref1.Col());
                nMinCol = min(nMinCol, rData.Ref2.Col());
                nMinRow = min(nMinRow, rData.Ref1.Row());
                nMinRow = min(nMinRow, rData.Ref2.Row());
 
                nMaxCol = max(nMaxCol, rData.Ref1.Col());
                nMaxCol = max(nMaxCol, rData.Ref2.Col());
                nMaxRow = max(nMaxRow, rData.Ref1.Row());
                nMaxRow = max(nMaxRow, rData.Ref2.Row());
            }
            break;
            default:
                ;
        }
    }
 
    const auto & rSheetLimits = pDoc->GetSheetLimits();
    if (nMinRow >= nMaxRow || nMinCol >= nMaxCol ||
        nMinRow >= rSheetLimits.GetMaxRowCount() || nMinCol >= rSheetLimits.GetMaxColCount() ||
        nMaxRow >= rSheetLimits.GetMaxRowCount() || nMaxCol >= rSheetLimits.GetMaxColCount())
    {
        // Invalid range.  Bail out.
        return false;
    }
 
    // Check if the following conditions are met:
 
    // 1) The upper-left corner cell is not included.
    // 2) The three adjacent cells of that corner cell are included.
 
    bool bRight = false, bBottom = false, bDiagonal = false;
    for (const auto& rxToken : rRefTokens)
    {
        switch (rxToken->GetType())
        {
            case svSingleRef:
            case svExternalSingleRef:
            {
                const ScSingleRefData& rData = *rxToken->GetSingleRef();
                if (rData.Col() == nMinCol && rData.Row() == nMinRow)
                    // The corner cell is contained.
                    return false;
 
                if (rData.Col() == nMinCol+nCornerColumnCount && rData.Row() == nMinRow)
                    bRight = true;
 
                if (rData.Col() == nMinCol && rData.Row() == nMinRow+nCornerRowCount)
                    bBottom = true;
 
                if (rData.Col() == nMinCol+nCornerColumnCount && rData.Row() == nMinRow+nCornerRowCount)
                    bDiagonal = true;
            }
            break;
            case svDoubleRef:
            case svExternalDoubleRef:
            {
                const ScComplexRefData& rData = *rxToken->GetDoubleRef();
                const ScSingleRefData& r1 = rData.Ref1;
                const ScSingleRefData& r2 = rData.Ref2;
                if (r1.Col() <= nMinCol && nMinCol <= r2.Col() &&
                    r1.Row() <= nMinRow && nMinRow <= r2.Row())
                    // The corner cell is contained.
                    return false;
 
                if (r1.Col() <= nMinCol+nCornerColumnCount && nMinCol+nCornerColumnCount <= r2.Col() &&
                    r1.Row() <= nMinRow && nMinRow <= r2.Row())
                    bRight = true;
 
                if (r1.Col() <= nMinCol && nMinCol <= r2.Col() &&
                    r1.Row() <= nMinRow+nCornerRowCount && nMinRow+nCornerRowCount <= r2.Row())
                    bBottom = true;
 
                if (r1.Col() <= nMinCol+nCornerColumnCount && nMinCol+nCornerColumnCount <= r2.Col() &&
                    r1.Row() <= nMinRow+nCornerRowCount && nMinRow+nCornerRowCount <= r2.Row())
                    bDiagonal = true;
            }
            break;
            default:
                ;
        }
    }
 
    if (!bRight || !bBottom || !bDiagonal)
        // Not all the adjacent cells are included.  Bail out.
        return false;
 
    ScSingleRefData aData;
    aData.InitFlags();
    aData.SetFlag3D(true);
    aData.SetAbsCol(nMinCol);
    aData.SetAbsRow(nMinRow);
    aData.SetAbsTab(nTab);
 
    if( nCornerRowCount==1 && nCornerColumnCount==1 )
    {
        if (bExternal)
        {
            ScTokenRef pCorner(
                new ScExternalSingleRefToken(nFileId, std::move(aExtTabName), aData));
            ScRefTokenHelper::join(pDoc, rRefTokens, pCorner, ScAddress());
        }
        else
        {
            ScTokenRef pCorner(new ScSingleRefToken(pDoc->GetSheetLimits(), aData));
            ScRefTokenHelper::join(pDoc, rRefTokens, pCorner, ScAddress());
        }
    }
    else
    {
        ScSingleRefData aDataEnd(aData);
        aDataEnd.IncCol(nCornerColumnCount-1);
        aDataEnd.IncRow(nCornerRowCount-1);
        ScComplexRefData r;
        r.Ref1=aData;
        r.Ref2=aDataEnd;
        if (bExternal)
        {
            ScTokenRef pCorner(
                new ScExternalDoubleRefToken(nFileId, std::move(aExtTabName), r));
            ScRefTokenHelper::join(pDoc, rRefTokens, pCorner, ScAddress());
        }
        else
        {
            ScTokenRef pCorner(new ScDoubleRefToken(pDoc->GetSheetLimits(), r));
            ScRefTokenHelper::join(pDoc, rRefTokens, pCorner, ScAddress());
        }
    }
 
    return true;
}
 
#define SHRINK_RANGE_THRESHOLD 10000
 
class ShrinkRefTokenToDataRange
{
    ScDocument* mpDoc;
public:
    explicit ShrinkRefTokenToDataRange(ScDocument* pDoc) : mpDoc(pDoc) {}
    void operator() (const ScTokenRef& rRef)
    {
        if (ScRefTokenHelper::isExternalRef(rRef))
            return;
 
        // Don't assume an ScDoubleRefToken if it isn't. It can be at least an
        // ScSingleRefToken, then there isn't anything to shrink.
        if (rRef->GetType() != svDoubleRef)
            return;
 
        ScComplexRefData& rData = *rRef->GetDoubleRef();
        ScSingleRefData& s = rData.Ref1;
        ScSingleRefData& e = rData.Ref2;
 
        if(abs((e.Col()-s.Col())*(e.Row()-s.Row())) < SHRINK_RANGE_THRESHOLD)
            return;
 
        SCCOL nMinCol = mpDoc->MaxCol(), nMaxCol = 0;
        SCROW nMinRow = mpDoc->MaxRow(), nMaxRow = 0;
 
        // Determine the smallest range that encompasses the data ranges of all sheets.
        SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
        for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
        {
            SCCOL nCol1 = 0, nCol2 = mpDoc->MaxCol();
            SCROW nRow1 = 0, nRow2 = mpDoc->MaxRow();
            mpDoc->ShrinkToDataArea(nTab, nCol1, nRow1, nCol2, nRow2);
            nMinCol = std::min(nMinCol, nCol1);
            nMinRow = std::min(nMinRow, nRow1);
            nMaxCol = std::max(nMaxCol, nCol2);
            nMaxRow = std::max(nMaxRow, nRow2);
        }
 
        // Shrink range to the data range if applicable.
        if (s.Col() < nMinCol)
            s.SetAbsCol(nMinCol);
        if (s.Row() < nMinRow)
            s.SetAbsRow(nMinRow);
        if (e.Col() > nMaxCol)
            e.SetAbsCol(nMaxCol);
        if (e.Row() > nMaxRow)
            e.SetAbsRow(nMaxRow);
    }
};
 
void shrinkToDataRange(ScDocument* pDoc, vector<ScTokenRef>& rRefTokens)
{
    std::for_each(rRefTokens.begin(), rRefTokens.end(), ShrinkRefTokenToDataRange(pDoc));
}
 
}
 
uno::Reference< chart2::data::XDataSource> SAL_CALL
ScChart2DataProvider::createDataSource(
    const uno::Sequence< beans::PropertyValue >& aArguments )
{
    SolarMutexGuard aGuard;
    if ( ! m_pDocument )
        throw uno::RuntimeException();
 
    uno::Reference< chart2::data::XDataSource> xResult;
    bool bLabel = true;
    bool bCategories = false;
    bool bOrientCol = true;
    OUString aRangeRepresentation;
    uno::Sequence< sal_Int32 > aSequenceMapping;
    bool bTimeBased = false;
    for(const auto& rArgument : aArguments)
    {
        if ( rArgument.Name == "DataRowSource" )
        {
            chart::ChartDataRowSource eSource = chart::ChartDataRowSource_COLUMNS;
            if( ! (rArgument.Value >>= eSource))
            {
                sal_Int32 nSource(0);
                if( rArgument.Value >>= nSource )
                    eSource = static_cast< chart::ChartDataRowSource >( nSource );
            }
            bOrientCol = (eSource == chart::ChartDataRowSource_COLUMNS);
        }
        else if ( rArgument.Name == "FirstCellAsLabel" )
        {
            bLabel = ::cppu::any2bool(rArgument.Value);
        }
        else if ( rArgument.Name == "HasCategories" )
        {
            bCategories = ::cppu::any2bool(rArgument.Value);
        }
        else if ( rArgument.Name == "CellRangeRepresentation" )
        {
            rArgument.Value >>= aRangeRepresentation;
        }
        else if ( rArgument.Name == "SequenceMapping" )
        {
            rArgument.Value >>= aSequenceMapping;
        }
        else if ( rArgument.Name == "TimeBased" )
        {
            rArgument.Value >>= bTimeBased;
        }
    }
 
    vector<ScTokenRef> aRefTokens;
    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
    ScRefTokenHelper::compileRangeRepresentation(
        aRefTokens, aRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
    if (aRefTokens.empty())
        // Invalid range representation.  Bail out.
        throw lang::IllegalArgumentException();
 
    SCTAB nTimeBasedStart = MAXTAB;
    SCTAB nTimeBasedEnd = 0;
    if(bTimeBased)
    {
        // limit to first sheet
        for(const auto& rxToken : aRefTokens)
        {
            if (rxToken->GetType() != svDoubleRef)
                continue;
 
            ScComplexRefData& rData = *rxToken->GetDoubleRef();
            ScSingleRefData& s = rData.Ref1;
            ScSingleRefData& e = rData.Ref2;
 
            nTimeBasedStart = std::min(nTimeBasedStart, s.Tab());
            nTimeBasedEnd = std::min(nTimeBasedEnd, e.Tab());
 
            if(s.Tab() != e.Tab())
                e.SetAbsTab(s.Tab());
        }
    }
 
    if(!bTimeBased)
        shrinkToDataRange(m_pDocument, aRefTokens);
 
    if (bLabel)
        lcl_addUpperLeftCornerIfMissing(m_pDocument, aRefTokens, 1, 1); //#i90669#
 
    bool bColHeaders = (bOrientCol ? bLabel : bCategories );
    bool bRowHeaders = (bOrientCol ? bCategories : bLabel );
 
    Chart2Positioner aChPositioner(m_pDocument, aRefTokens);
    aChPositioner.setHeaders(bColHeaders, bRowHeaders);
 
    const Chart2PositionMap* pChartMap = aChPositioner.getPositionMap();
    if (!pChartMap)
        // No chart position map instance.  Bail out.
        return xResult;
 
    rtl::Reference<ScChart2DataSource> pDS;
    ::std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > aSeqs;
 
    // Fill Categories
    if( bCategories )
    {
        vector<ScTokenRef> aValueTokens;
        if (bOrientCol)
            aValueTokens = pChartMap->getAllRowHeaderRanges();
        else
            aValueTokens = pChartMap->getAllColHeaderRanges();
 
        vector<ScTokenRef> aLabelTokens(
                pChartMap->getLeftUpperCornerRanges());
 
        uno::Reference< chart2::data::XLabeledDataSequence > xCategories = lcl_createLabeledDataSequenceFromTokens(
            std::move(aValueTokens), std::move(aLabelTokens), m_pDocument, m_bIncludeHiddenCells ); //ownership of pointers is transferred!
        if ( xCategories.is() )
        {
            aSeqs.push_back( xCategories );
        }
    }
 
    // Fill Series (values and label)
    sal_Int32 nCount = bOrientCol ? pChartMap->getDataColCount() : pChartMap->getDataRowCount();
    for (sal_Int32 i = 0; i < nCount; ++i)
    {
        vector<ScTokenRef> aValueTokens;
        vector<ScTokenRef> aLabelTokens;
        if (bOrientCol)
        {
            aValueTokens = pChartMap->getDataColRanges(static_cast<SCCOL>(i));
            aLabelTokens = pChartMap->getColHeaderRanges(static_cast<SCCOL>(i));
        }
        else
        {
            aValueTokens = pChartMap->getDataRowRanges(static_cast<SCROW>(i));
            aLabelTokens = pChartMap->getRowHeaderRanges(static_cast<SCROW>(i));
        }
        uno::Reference< chart2::data::XLabeledDataSequence > xChartSeries = lcl_createLabeledDataSequenceFromTokens(
            std::move(aValueTokens), std::move(aLabelTokens), m_pDocument, m_bIncludeHiddenCells ); //ownership of pointers is transferred!
        if ( xChartSeries.is() && xChartSeries->getValues().is() && xChartSeries->getValues()->getData().hasElements() )
        {
            aSeqs.push_back( xChartSeries );
        }
    }
 
    pDS = new ScChart2DataSource(m_pDocument);
 
    //reorder labeled sequences according to aSequenceMapping
    ::std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > aSeqVector;
    aSeqVector.reserve(aSeqs.size());
    for (auto const& aSeq : aSeqs)
    {
        aSeqVector.push_back(aSeq);
    }
 
    for (const sal_Int32 nNewIndex : aSequenceMapping)
    {
        // note: assuming that the values in the sequence mapping are always non-negative
        ::std::vector< uno::Reference< chart2::data::XLabeledDataSequence > >::size_type nOldIndex( static_cast< sal_uInt32 >( nNewIndex ) );
        if( nOldIndex < aSeqVector.size() )
        {
            pDS->AddLabeledSequence( aSeqVector[nOldIndex] );
            aSeqVector[nOldIndex] = nullptr;
        }
    }
 
    for(const uno::Reference< chart2::data::XLabeledDataSequence >& xSeq : aSeqVector)
    {
        if ( xSeq.is() )
        {
            pDS->AddLabeledSequence( xSeq );
        }
    }
 
    xResult.set( pDS );
    return xResult;
}
 
namespace
{
 
/**
 * Function object to create a list of table numbers from a token list.
 */
class InsertTabNumber
{
public:
    InsertTabNumber() :
        mpTabNumVector(std::make_shared<vector<SCTAB>>())
    {
    }
 
    void operator() (const ScTokenRef& pToken) const
    {
        if (!ScRefTokenHelper::isRef(pToken))
            return;
 
        const ScSingleRefData& r = *pToken->GetSingleRef();
        mpTabNumVector->push_back(r.Tab());
    }
 
    void getVector(vector<SCTAB>& rVector)
    {
        mpTabNumVector->swap(rVector);
    }
private:
    shared_ptr< vector<SCTAB> > mpTabNumVector;
};
 
class RangeAnalyzer
{
public:
    RangeAnalyzer();
    void initRangeAnalyzer( const ScDocument* pDoc, const vector<ScTokenRef>& rTokens );
    void analyzeRange( sal_Int32& rnDataInRows, sal_Int32& rnDataInCols,
            bool& rbRowSourceAmbiguous ) const;
    bool inSameSingleRow( const RangeAnalyzer& rOther );
    bool inSameSingleColumn( const RangeAnalyzer& rOther );
    SCROW getRowCount() const { return mnRowCount; }
    SCCOL getColumnCount() const { return mnColumnCount; }
 
private:
    bool mbEmpty;
    bool mbAmbiguous;
    SCROW mnRowCount;
    SCCOL mnColumnCount;
 
    SCCOL mnStartColumn;
    SCROW mnStartRow;
};
 
RangeAnalyzer::RangeAnalyzer()
    : mbEmpty(true)
    , mbAmbiguous(false)
    , mnRowCount(0)
    , mnColumnCount(0)
    , mnStartColumn(-1)
    , mnStartRow(-1)
{
}
 
void RangeAnalyzer::initRangeAnalyzer( const ScDocument* pDoc, const vector<ScTokenRef>& rTokens )
{
    mnRowCount=0;
    mnColumnCount=0;
    mnStartColumn = -1;
    mnStartRow = -1;
    mbAmbiguous=false;
    if( rTokens.empty() )
    {
        mbEmpty=true;
        return;
    }
    mbEmpty=false;
 
    for (const ScTokenRef& aRefToken : rTokens)
    {
        StackVar eVar = aRefToken->GetType();
        if (eVar == svDoubleRef || eVar == svExternalDoubleRef)
        {
            const ScComplexRefData& r = *aRefToken->GetDoubleRef();
            if (r.Ref1.Tab() == r.Ref2.Tab())
            {
                mnColumnCount = std::max<SCCOL>(mnColumnCount, static_cast<SCCOL>(abs(r.Ref2.Col() - r.Ref1.Col())+1));
                mnRowCount = std::max<SCROW>(mnRowCount, static_cast<SCROW>(abs(r.Ref2.Row() - r.Ref1.Row())+1));
                if( mnStartColumn == -1 )
                {
                    mnStartColumn = r.Ref1.Col();
                    mnStartRow = r.Ref1.Row();
                }
                else
                {
                    if (mnStartColumn != r.Ref1.Col() && mnStartRow != r.Ref1.Row())
                        mbAmbiguous=true;
                }
            }
            else
                mbAmbiguous=true;
        }
        else if (eVar == svSingleRef || eVar == svExternalSingleRef)
        {
            const ScSingleRefData& r = *aRefToken->GetSingleRef();
            mnColumnCount = std::max<SCCOL>( mnColumnCount, 1);
            mnRowCount = std::max<SCROW>( mnRowCount, 1);
            if( mnStartColumn == -1 )
            {
                mnStartColumn = r.Col();
                mnStartRow = r.Row();
            }
            else
            {
                if (mnStartColumn != r.Col() && mnStartRow != r.Row())
                    mbAmbiguous=true;
            }
        }
        else if (eVar == svIndex && aRefToken->GetOpCode() == ocName)
        {
            ScRangeData* pNameRange = pDoc->FindRangeNameBySheetAndIndex(aRefToken->GetSheet(), aRefToken->GetIndex());
            ScRange aRange;
            if (pNameRange->IsReference(aRange))
            {
                mnColumnCount = std::max<SCCOL>(mnColumnCount, static_cast<SCCOL>(abs(aRange.aEnd.Col() - aRange.aStart.Col()) + 1));
                mnRowCount = std::max<SCROW>(mnRowCount, static_cast<SCROW>(abs(aRange.aEnd.Row() - aRange.aStart.Row()) + 1));
                if (mnStartColumn == -1)
                {
                    mnStartColumn = aRange.aStart.Col();
                    mnStartRow = aRange.aStart.Row();
                }
                else
                {
                    if (mnStartColumn != aRange.aStart.Col() && mnStartRow != aRange.aStart.Row())
                        mbAmbiguous = true;
                }
            }
            else
                mbAmbiguous = true;
        }
        else
            mbAmbiguous=true;
    }
}
 
void RangeAnalyzer::analyzeRange( sal_Int32& rnDataInRows,
                                     sal_Int32& rnDataInCols,
                                     bool& rbRowSourceAmbiguous ) const
{
    if(!mbEmpty && !mbAmbiguous)
    {
        if( mnRowCount==1 && mnColumnCount>1 )
            ++rnDataInRows;
        else if( mnColumnCount==1 && mnRowCount>1 )
            ++rnDataInCols;
        else if( mnRowCount>1 && mnColumnCount>1 )
            rbRowSourceAmbiguous = true;
    }
    else if( !mbEmpty )
        rbRowSourceAmbiguous = true;
}
 
bool RangeAnalyzer::inSameSingleRow( const RangeAnalyzer& rOther )
{
    return mnStartRow==rOther.mnStartRow &&
        mnRowCount==1 && rOther.mnRowCount==1;
}
 
bool RangeAnalyzer::inSameSingleColumn( const RangeAnalyzer& rOther )
{
    return mnStartColumn==rOther.mnStartColumn &&
        mnColumnCount==1 && rOther.mnColumnCount==1;
}
 
std::pair<OUString, OUString> constructKey(const uno::Reference< chart2::data::XLabeledDataSequence>& xNew)
{
    std::pair<OUString, OUString> aKey;
    if( xNew->getLabel().is() )
        aKey.first = xNew->getLabel()->getSourceRangeRepresentation();
    if( xNew->getValues().is() )
        aKey.second = xNew->getValues()->getSourceRangeRepresentation();
    return aKey;
}
 
 
} //end anonymous namespace
 
uno::Sequence< beans::PropertyValue > SAL_CALL ScChart2DataProvider::detectArguments(
    const uno::Reference< chart2::data::XDataSource >& xDataSource )
{
    ::std::vector< beans::PropertyValue > aResult;
    bool bRowSourceDetected = false;
    bool bFirstCellAsLabel = false;
    bool bHasCategories = false;
    OUString sRangeRep;
 
    bool bHasCategoriesLabels = false;
    vector<ScTokenRef> aAllCategoriesValuesTokens;
    vector<ScTokenRef> aAllSeriesLabelTokens;
 
    chart::ChartDataRowSource eRowSource = chart::ChartDataRowSource_COLUMNS;
 
    vector<ScTokenRef> aAllTokens;
 
    // parse given data source and collect infos
    {
        SolarMutexGuard aGuard;
        OSL_ENSURE( m_pDocument, "No Document -> no detectArguments" );
        if(!m_pDocument ||!xDataSource.is())
            return comphelper::containerToSequence( aResult );
 
        sal_Int32 nDataInRows = 0;
        sal_Int32 nDataInCols = 0;
        bool bRowSourceAmbiguous = false;
 
        const Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aSequences( xDataSource->getDataSequences());
        const sal_Int32 nCount( aSequences.getLength());
        RangeAnalyzer aPrevLabel,aPrevValues;
        for( const uno::Reference< chart2::data::XLabeledDataSequence >& xLS : aSequences )
        {
            if( xLS.is() )
            {
                bool bThisIsCategories = false;
                if(!bHasCategories)
                {
                    uno::Reference< beans::XPropertySet > xSeqProp( xLS->getValues(), uno::UNO_QUERY );
                    OUString aRole;
                    if( xSeqProp.is() && (xSeqProp->getPropertyValue(u"Role"_ustr) >>= aRole) &&
                        aRole == "categories" )
                        bThisIsCategories = bHasCategories = true;
                }
 
                RangeAnalyzer aLabel,aValues;
                // label
                uno::Reference< chart2::data::XDataSequence > xLabel( xLS->getLabel());
                if( xLabel.is())
                {
                    bFirstCellAsLabel = true;
                    vector<ScTokenRef> aTokens;
                    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
                    ScRefTokenHelper::compileRangeRepresentation(
                        aTokens, xLabel->getSourceRangeRepresentation(), *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
                    aLabel.initRangeAnalyzer(m_pDocument, aTokens);
                    for (const auto& rxToken : aTokens)
                    {
                        if (rxToken->GetType() == svIndex && rxToken->GetOpCode() == ocName)
                        {
                            ScRangeData* pNameRange = m_pDocument->FindRangeNameBySheetAndIndex(rxToken->GetSheet(), rxToken->GetIndex());
                            if (pNameRange->HasReferences())
                            {
                                const ScTokenRef aTempToken = pNameRange->GetCode()->FirstToken();
                                ScRefTokenHelper::join(m_pDocument, aAllTokens, aTempToken, ScAddress());
                            }
                            else
                                ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress());
                        }
                        else
                            ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress());
                        if(!bThisIsCategories)
                            ScRefTokenHelper::join(m_pDocument, aAllSeriesLabelTokens, rxToken, ScAddress());
                    }
                    if(bThisIsCategories)
                        bHasCategoriesLabels=true;
                }
                // values
                uno::Reference< chart2::data::XDataSequence > xValues( xLS->getValues());
                if( xValues.is())
                {
                    vector<ScTokenRef> aTokens;
                    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
                    ScRefTokenHelper::compileRangeRepresentation(
                        aTokens, xValues->getSourceRangeRepresentation(), *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
                    aValues.initRangeAnalyzer(m_pDocument, aTokens);
                    for (const auto& rxToken : aTokens)
                    {
                        if (rxToken->GetType() == svIndex && rxToken->GetOpCode() == ocName)
                        {
                            ScRangeData* pNameRange = m_pDocument->FindRangeNameBySheetAndIndex(rxToken->GetSheet(), rxToken->GetIndex());
                            if (pNameRange->HasReferences())
                            {
                                const ScTokenRef aTempToken = pNameRange->GetCode()->FirstToken();
                                ScRefTokenHelper::join(m_pDocument, aAllTokens, aTempToken, ScAddress());
                            }
                            else
                                ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress());
                        }
                        else
                            ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress());
                        if(bThisIsCategories)
                            ScRefTokenHelper::join(m_pDocument, aAllCategoriesValuesTokens, rxToken, ScAddress());
                    }
                }
                //detect row source
                if(!bThisIsCategories || nCount==1) //categories might span multiple rows *and* columns, so they should be used for detection only if nothing else is available
                {
                    if (!bRowSourceAmbiguous)
                    {
                        aValues.analyzeRange(nDataInRows,nDataInCols,bRowSourceAmbiguous);
                        aLabel.analyzeRange(nDataInRows,nDataInCols,bRowSourceAmbiguous);
                        if (nDataInRows > 1 && nDataInCols > 1)
                            bRowSourceAmbiguous = true;
                        else if( !bRowSourceAmbiguous && !nDataInRows && !nDataInCols )
                        {
                            if( aValues.inSameSingleColumn( aLabel ) )
                                nDataInCols++;
                            else if( aValues.inSameSingleRow( aLabel ) )
                                nDataInRows++;
                            else
                            {
                                //#i86188# also detect a single column split into rows correctly
                                if( aValues.inSameSingleColumn( aPrevValues ) )
                                    nDataInRows++;
                                else if( aValues.inSameSingleRow( aPrevValues ) )
                                    nDataInCols++;
                                else if( aLabel.inSameSingleColumn( aPrevLabel ) )
                                    nDataInRows++;
                                else if( aLabel.inSameSingleRow( aPrevLabel ) )
                                    nDataInCols++;
                            }
                        }
                    }
                }
                aPrevValues=aValues;
                aPrevLabel=aLabel;
            }
        }
 
        if (!bRowSourceAmbiguous)
        {
            bRowSourceDetected = true;
            eRowSource = ( nDataInCols > 0
                           ? chart::ChartDataRowSource_COLUMNS
                           : chart::ChartDataRowSource_ROWS );
        }
        else
        {
            // set DataRowSource to the better of the two ambiguities
            eRowSource = ( nDataInRows > nDataInCols
                           ? chart::ChartDataRowSource_ROWS
                           : chart::ChartDataRowSource_COLUMNS );
        }
 
    }
 
    // TableNumberList
    {
        vector<SCTAB> aTableNumVector;
        InsertTabNumber func;
        func = ::std::for_each(aAllTokens.begin(), aAllTokens.end(), func);
        func.getVector(aTableNumVector);
        aResult.emplace_back( "TableNumberList", -1,
                                  uno::Any( lcl_createTableNumberList( aTableNumVector ) ),
                                  beans::PropertyState_DIRECT_VALUE );
    }
 
    if( bRowSourceDetected )
    {
        // DataRowSource (calculated before)
        aResult.emplace_back( "DataRowSource", -1,
                                  uno::Any( eRowSource ), beans::PropertyState_DIRECT_VALUE );
        // HasCategories
        aResult.emplace_back( "HasCategories", -1,
                                  uno::Any( bHasCategories ), beans::PropertyState_DIRECT_VALUE );
        // FirstCellAsLabel
        aResult.emplace_back( "FirstCellAsLabel", -1,
                                  uno::Any( bFirstCellAsLabel ), beans::PropertyState_DIRECT_VALUE );
    }
 
    // Add the left upper corner to the range if it is missing.
    if (bRowSourceDetected && bFirstCellAsLabel && bHasCategories && !bHasCategoriesLabels )
    {
        RangeAnalyzer aTop,aLeft;
        if( eRowSource==chart::ChartDataRowSource_COLUMNS )
        {
            aTop.initRangeAnalyzer(m_pDocument, aAllSeriesLabelTokens);
            aLeft.initRangeAnalyzer(m_pDocument, aAllCategoriesValuesTokens);
        }
        else
        {
            aTop.initRangeAnalyzer(m_pDocument, aAllCategoriesValuesTokens);
            aLeft.initRangeAnalyzer(m_pDocument, aAllSeriesLabelTokens);
        }
        lcl_addUpperLeftCornerIfMissing(m_pDocument, aAllTokens, aTop.getRowCount(), aLeft.getColumnCount());//e.g. #i91212#
    }
 
    // Get range string.
    lcl_convertTokensToString(sRangeRep, aAllTokens, *m_pDocument);
 
    // add cell range property
    aResult.emplace_back( "CellRangeRepresentation", -1,
                              uno::Any( sRangeRep ), beans::PropertyState_DIRECT_VALUE );
 
    //Sequence Mapping
    bool const bSequencesReordered = true;//todo detect this above or detect this sequence mapping cheaper ...
    if( bSequencesReordered && bRowSourceDetected )
    {
        bool bDifferentIndexes = false;
 
        std::vector< sal_Int32 > aSequenceMappingVector;
 
        uno::Reference< chart2::data::XDataSource > xCompareDataSource;
        try
        {
            xCompareDataSource.set( createDataSource( comphelper::containerToSequence( aResult ) ) );
        }
        catch( const lang::IllegalArgumentException & )
        {
            // creation of data source to compare didn't work, so we cannot
            // create a sequence mapping
        }
 
        if( xDataSource.is() && xCompareDataSource.is() )
        {
            const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence> > aOldSequences =
                xCompareDataSource->getDataSequences();
            const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence> > aNewSequences =
                xDataSource->getDataSequences();
 
            std::map<std::pair<OUString, OUString>,sal_Int32> aOldEntryToIndex;
            for( sal_Int32 nIndex = 0, n = aOldSequences.getLength(); nIndex < n; nIndex++ )
            {
                const uno::Reference< chart2::data::XLabeledDataSequence>& xOld( aOldSequences[nIndex] );
                if( xOld.is() )
                {
                    std::pair<OUString, OUString> aKey = constructKey(xOld);
                    aOldEntryToIndex[aKey] = nIndex;
                }
            }
 
            for( sal_Int32 nNewIndex = 0, n = aNewSequences.getLength(); nNewIndex < n; nNewIndex++ )
            {
                const uno::Reference< chart2::data::XLabeledDataSequence>& xNew( aNewSequences[nNewIndex] );
                if( !xNew.is() )
                    continue;
 
                std::pair<OUString, OUString> aKey = constructKey(xNew);
                if (aOldEntryToIndex.find(aKey) == aOldEntryToIndex.end())
                    continue;
 
                sal_Int32 nOldIndex = aOldEntryToIndex[aKey];
                if( nOldIndex != nNewIndex )
                    bDifferentIndexes = true;
 
                aSequenceMappingVector.push_back(nOldIndex);
            }
        }
 
        if( bDifferentIndexes && !aSequenceMappingVector.empty() )
        {
            aResult.emplace_back( "SequenceMapping", -1,
                    uno::Any( comphelper::containerToSequence(aSequenceMappingVector) )
                    , beans::PropertyState_DIRECT_VALUE );
        }
    }
 
    return comphelper::containerToSequence( aResult );
}
 
sal_Bool SAL_CALL ScChart2DataProvider::createDataSequenceByRangeRepresentationPossible( const OUString& aRangeRepresentation )
{
    SolarMutexGuard aGuard;
    if( ! m_pDocument )
        return false;
 
    vector<ScTokenRef> aTokens;
    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
    ScRefTokenHelper::compileRangeRepresentation(
        aTokens, aRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
    return !aTokens.empty();
}
 
uno::Reference< chart2::data::XDataSequence > SAL_CALL
    ScChart2DataProvider::createDataSequenceByRangeRepresentation(
    const OUString& aRangeRepresentation )
{
    SolarMutexGuard aGuard;
    uno::Reference< chart2::data::XDataSequence > xResult;
 
    OSL_ENSURE( m_pDocument, "No Document -> no createDataSequenceByRangeRepresentation" );
    if(!m_pDocument || aRangeRepresentation.isEmpty())
        return xResult;
 
    vector<ScTokenRef> aRefTokens;
    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
    ScRefTokenHelper::compileRangeRepresentation(
        aRefTokens, aRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
    if (aRefTokens.empty())
        return xResult;
 
    shrinkToDataRange(m_pDocument, aRefTokens);
 
    xResult.set(new ScChart2DataSequence(m_pDocument, std::move(aRefTokens), m_bIncludeHiddenCells));
 
    return xResult;
}
 
uno::Reference<chart2::data::XDataSequence> SAL_CALL
ScChart2DataProvider::createDataSequenceByValueArray(
    const OUString& /*aRole*/, const OUString& /*aRangeRepresentation*/,
    const OUString& /*aRoleQualifier*/ )
{
    return uno::Reference<chart2::data::XDataSequence>();
}
 
uno::Reference< sheet::XRangeSelection > SAL_CALL ScChart2DataProvider::getRangeSelection()
{
    uno::Reference< sheet::XRangeSelection > xResult;
 
    uno::Reference< frame::XModel > xModel( lcl_GetXModel( m_pDocument ));
    if( xModel.is())
        xResult.set( xModel->getCurrentController(), uno::UNO_QUERY );
 
    return xResult;
}
 
sal_Bool SAL_CALL ScChart2DataProvider::createDataSequenceByFormulaTokensPossible(
    const Sequence<sheet::FormulaToken>& aTokens )
{
    if (!aTokens.hasElements())
        return false;
 
    ScTokenArray aCode(*m_pDocument);
    if (!ScTokenConversion::ConvertToTokenArray(*m_pDocument, aCode, aTokens))
        return false;
 
    sal_uInt16 n = aCode.GetLen();
    if (!n)
        return false;
 
    formula::FormulaTokenArrayPlainIterator aIter(aCode);
    const formula::FormulaToken* pFirst = aIter.First();
    const formula::FormulaToken* pLast = aCode.GetArray()[n-1];
    for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
    {
        switch (p->GetType())
        {
            case svSep:
            {
                switch (p->GetOpCode())
                {
                    case ocSep:
                        // separators are allowed.
                    break;
                    case ocOpen:
                        if (p != pFirst)
                            // open paran is allowed only as the first token.
                            return false;
                    break;
                    case ocClose:
                        if (p != pLast)
                            // close paren is allowed only as the last token.
                            return false;
                    break;
                    default:
                        return false;
                }
            }
            break;
            case svSingleRef:
            case svDoubleRef:
            case svExternalSingleRef:
            case svExternalDoubleRef:
            break;
            default:
                return false;
        }
    }
 
    return true;
}
 
uno::Reference<chart2::data::XDataSequence> SAL_CALL
ScChart2DataProvider::createDataSequenceByFormulaTokens(
    const Sequence<sheet::FormulaToken>& aTokens )
{
    uno::Reference<chart2::data::XDataSequence> xResult;
    if (!aTokens.hasElements())
        return xResult;
 
    ScTokenArray aCode(*m_pDocument);
    if (!ScTokenConversion::ConvertToTokenArray(*m_pDocument, aCode, aTokens))
        return xResult;
 
    sal_uInt16 n = aCode.GetLen();
    if (!n)
        return xResult;
 
    vector<ScTokenRef> aRefTokens;
    formula::FormulaTokenArrayPlainIterator aIter(aCode);
    const formula::FormulaToken* pFirst = aIter.First();
    const formula::FormulaToken* pLast = aCode.GetArray()[n-1];
    for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
    {
        switch (p->GetType())
        {
            case svSep:
            {
                switch (p->GetOpCode())
                {
                    case ocSep:
                        // separators are allowed.
                    break;
                    case ocOpen:
                        if (p != pFirst)
                            // open paran is allowed only as the first token.
                            throw lang::IllegalArgumentException();
                    break;
                    case ocClose:
                        if (p != pLast)
                            // close paren is allowed only as the last token.
                            throw lang::IllegalArgumentException();
                    break;
                    default:
                        throw lang::IllegalArgumentException();
                }
            }
            break;
            case svIndex:
            case svString:
            case svSingleRef:
            case svDoubleRef:
            case svExternalSingleRef:
            case svExternalDoubleRef:
            {
                ScTokenRef pNew(p->Clone());
                aRefTokens.push_back(pNew);
            }
            break;
            default:
                throw lang::IllegalArgumentException();
        }
    }
 
    if (aRefTokens.empty())
        return xResult;
 
    shrinkToDataRange(m_pDocument, aRefTokens);
 
    xResult.set(new ScChart2DataSequence(m_pDocument, std::move(aRefTokens), m_bIncludeHiddenCells));
    return xResult;
}
 
// XRangeXMLConversion ---------------------------------------------------
 
OUString SAL_CALL ScChart2DataProvider::convertRangeToXML( const OUString& sRangeRepresentation )
{
    OUString aRet;
    if (!m_pDocument)
        return aRet;
 
    if (sRangeRepresentation.isEmpty())
        // Empty data range is allowed.
        return aRet;
 
    vector<ScTokenRef> aRefTokens;
    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
    ScRefTokenHelper::compileRangeRepresentation(
        aRefTokens, sRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
    if (aRefTokens.empty())
    {
        SAL_WARN("sc", "convertRangeToXML throw IllegalArgumentException from input of: " << sRangeRepresentation);
        throw lang::IllegalArgumentException();
    }
 
    Tokens2RangeStringXML converter(*m_pDocument);
    converter = ::std::for_each(aRefTokens.begin(), aRefTokens.end(), converter);
    converter.getString(aRet);
 
    return aRet;
}
 
OUString SAL_CALL ScChart2DataProvider::convertRangeFromXML( const OUString& sXMLRange )
{
    if (!m_pDocument)
    {
        // #i74062# When loading flat XML, this is called before the referenced sheets are in the document,
        // so the conversion has to take place directly with the strings, without looking up the sheets.
 
        OUStringBuffer sRet;
        sal_Int32 nOffset = 0;
        while( nOffset >= 0 )
        {
            OUString sToken;
            ScRangeStringConverter::GetTokenByOffset( sToken, sXMLRange, nOffset );
            if( nOffset >= 0 )
            {
                // convert one address (remove dots)
 
                OUString aUIString(sToken);
 
                sal_Int32 nIndex = ScRangeStringConverter::IndexOf( sToken, ':', 0 );
                if ( nIndex >= 0 && nIndex < aUIString.getLength() - 1 &&
                        aUIString[nIndex + 1] == '.' )
                    aUIString = aUIString.replaceAt( nIndex + 1, 1, u"" );
 
                if ( aUIString[0] == '.' )
                    aUIString = aUIString.copy( 1 );
 
                if( !sRet.isEmpty() )
                    sRet.append( ';' );
                sRet.append( aUIString );
            }
        }
 
        return sRet.makeStringAndClear();
    }
 
    OUString aRet;
    ScRangeStringConverter::GetStringFromXMLRangeString(aRet, sXMLRange, *m_pDocument);
    return aRet;
}
 
// DataProvider XPropertySet -------------------------------------------------
 
uno::Reference< beans::XPropertySetInfo> SAL_CALL
ScChart2DataProvider::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
    static uno::Reference<beans::XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( m_aPropSet.getPropertyMap() );
    return aRef;
}
 
void SAL_CALL ScChart2DataProvider::setPropertyValue(
        const OUString& rPropertyName, const uno::Any& rValue)
{
    if ( rPropertyName != SC_UNONAME_INCLUDEHIDDENCELLS )
        throw beans::UnknownPropertyException(rPropertyName);
 
    if ( !(rValue >>= m_bIncludeHiddenCells))
        throw lang::IllegalArgumentException();
 
}
 
uno::Any SAL_CALL ScChart2DataProvider::getPropertyValue(
        const OUString& rPropertyName)
{
    uno::Any aRet;
    if ( rPropertyName == SC_UNONAME_INCLUDEHIDDENCELLS )
        aRet <<= m_bIncludeHiddenCells;
    else if (rPropertyName == SC_UNONAME_USE_INTERNAL_DATA_PROVIDER)
    {
        // This is a read-only property.
        aRet <<= m_pDocument->PastingDrawFromOtherDoc();
    }
    else
        throw beans::UnknownPropertyException(rPropertyName);
    return aRet;
}
 
void SAL_CALL ScChart2DataProvider::addPropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XPropertyChangeListener>& /*xListener*/)
{
    OSL_FAIL( "Not yet implemented" );
}
 
void SAL_CALL ScChart2DataProvider::removePropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XPropertyChangeListener>& /*rListener*/)
{
    OSL_FAIL( "Not yet implemented" );
}
 
void SAL_CALL ScChart2DataProvider::addVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XVetoableChangeListener>& /*rListener*/)
{
    OSL_FAIL( "Not yet implemented" );
}
 
void SAL_CALL ScChart2DataProvider::removeVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XVetoableChangeListener>& /*rListener*/ )
{
    OSL_FAIL( "Not yet implemented" );
}
 
// DataSource ================================================================
 
ScChart2DataSource::ScChart2DataSource( ScDocument* pDoc)
    : m_pDocument( pDoc)
{
    if ( m_pDocument )
        m_pDocument->AddUnoObject( *this);
}
 
ScChart2DataSource::~ScChart2DataSource()
{
    SolarMutexGuard g;
 
    if ( m_pDocument )
        m_pDocument->RemoveUnoObject( *this);
}
 
void ScChart2DataSource::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
{
    if ( rHint.GetId() == SfxHintId::Dying )
    {
        m_pDocument = nullptr;
    }
}
 
uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence> > SAL_CALL
ScChart2DataSource::getDataSequences()
{
    SolarMutexGuard aGuard;
    return comphelper::containerToSequence(m_aLabeledSequences);
}
 
void ScChart2DataSource::AddLabeledSequence(const uno::Reference < chart2::data::XLabeledDataSequence >& xNew)
{
    m_aLabeledSequences.push_back(xNew);
}
 
// DataSequence ==============================================================
 
ScChart2DataSequence::Item::Item()
    : mfValue(std::numeric_limits<double>::quiet_NaN())
    , mbIsValue(false)
{
}
 
ScChart2DataSequence::HiddenRangeListener::HiddenRangeListener(ScChart2DataSequence& rParent) :
    mrParent(rParent)
{
}
 
ScChart2DataSequence::HiddenRangeListener::~HiddenRangeListener()
{
}
 
void ScChart2DataSequence::HiddenRangeListener::notify()
{
    mrParent.setDataChangedHint(true);
}
 
ScChart2DataSequence::ScChart2DataSequence( ScDocument* pDoc,
        vector<ScTokenRef>&& rTokens,
        bool bIncludeHiddenCells )
    : m_xDataArray(new std::vector<Item>)
    , m_bIncludeHiddenCells( bIncludeHiddenCells)
    , m_nObjectId( 0 )
    , m_pDocument( pDoc)
    , m_aTokens(std::move(rTokens))
    , m_aPropSet(lcl_GetDataSequencePropertyMap())
    , m_bGotDataChangedHint(false)
    , m_bExtDataRebuildQueued(false)
    , mbTimeBased(false)
    , mnTimeBasedStart(0)
    , mnTimeBasedEnd(0)
    , mnCurrentTab(0)
{
    if ( m_pDocument )
    {
        m_pDocument->AddUnoObject( *this);
        m_nObjectId = m_pDocument->GetNewUnoId();
    }
    // FIXME: real implementation of identifier and it's mapping to ranges.
    // Reuse ScChartListener?
 
    // BM: don't use names of named ranges but the UI range strings
//  String  aStr;
//  rRangeList->Format( aStr, ScRefFlags::RANGE_ABS_3D, m_pDocument );
//    m_aIdentifier = aStr;
 
//      m_aIdentifier = "ID_";
//      static sal_Int32 nID = 0;
//      m_aIdentifier += OUString::valueOf( ++nID);
}
 
/** called from Clone() */
ScChart2DataSequence::ScChart2DataSequence(ScDocument* pDoc, const ScChart2DataSequence& r)
    : m_xDataArray(r.m_xDataArray)
    , m_aHiddenValues(r.m_aHiddenValues)
    , m_aRole(r.m_aRole)
    , m_bIncludeHiddenCells( r.m_bIncludeHiddenCells)
    , m_nObjectId( 0 )
    , m_pDocument( pDoc)
    , m_aPropSet(lcl_GetDataSequencePropertyMap())
    , m_bGotDataChangedHint(false)
    , m_bExtDataRebuildQueued(false)
    , mbTimeBased(false)
    , mnTimeBasedStart(0)
    , mnTimeBasedEnd(0)
    , mnCurrentTab(0)
{
    assert(pDoc);
 
    // Clone tokens.
    m_aTokens.reserve(r.m_aTokens.size());
    for (const auto& rxToken : r.m_aTokens)
    {
        ScTokenRef p(rxToken->Clone());
        m_aTokens.push_back(p);
    }
 
    m_pDocument->AddUnoObject( *this);
    m_nObjectId = m_pDocument->GetNewUnoId();
 
    if (r.m_oRangeIndices)
        m_oRangeIndices = *r.m_oRangeIndices;
 
    if (!r.m_pExtRefListener)
        return;
 
    // Re-register all external files that the old instance was
    // listening to.
 
    ScExternalRefManager* pRefMgr = m_pDocument->GetExternalRefManager();
    m_pExtRefListener.reset(new ExternalRefListener(*this, m_pDocument));
    const std::unordered_set<sal_uInt16>& rFileIds = r.m_pExtRefListener->getAllFileIds();
    for (const auto& rFileId : rFileIds)
    {
        pRefMgr->addLinkListener(rFileId, m_pExtRefListener.get());
        m_pExtRefListener->addFileId(rFileId);
    }
}
 
ScChart2DataSequence::~ScChart2DataSequence()
{
    SolarMutexGuard g;
 
    if ( m_pDocument )
    {
        m_pDocument->RemoveUnoObject( *this);
        if (m_pHiddenListener)
        {
            ScChartListenerCollection* pCLC = m_pDocument->GetChartListenerCollection();
            if (pCLC)
                pCLC->EndListeningHiddenRange(m_pHiddenListener.get());
        }
        StopListeningToAllExternalRefs();
    }
 
    m_pValueListener.reset();
}
 
void ScChart2DataSequence::RefChanged()
{
    if( !m_pValueListener || m_aValueListeners.empty() )
        return;
 
    m_pValueListener->EndListeningAll();
 
    if( !m_pDocument )
        return;
 
    ScChartListenerCollection* pCLC = nullptr;
    if (m_pHiddenListener)
    {
        pCLC = m_pDocument->GetChartListenerCollection();
        if (pCLC)
            pCLC->EndListeningHiddenRange(m_pHiddenListener.get());
    }
 
    for (const auto& rxToken : m_aTokens)
    {
        ScRange aRange;
        if (!ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, rxToken, ScAddress()))
            continue;
 
        m_pDocument->StartListeningArea(aRange, false, m_pValueListener.get());
        if (pCLC)
            pCLC->StartListeningHiddenRange(aRange, m_pHiddenListener.get());
    }
}
 
void ScChart2DataSequence::BuildDataCache()
{
    m_bExtDataRebuildQueued = false;
 
    if (!m_xDataArray->empty())
        return;
 
    StopListeningToAllExternalRefs();
 
    ::std::vector<sal_Int32> aHiddenValues;
    sal_Int32 nDataCount = 0;
 
    for (const auto& rxToken : m_aTokens)
    {
        if (ScRefTokenHelper::isExternalRef(rxToken))
        {
            nDataCount += FillCacheFromExternalRef(rxToken);
        }
        else
        {
            ScRange aRange;
            if (!ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, rxToken, ScAddress()))
                continue;
 
            SCCOL nLastCol = -1;
            SCROW nLastRow = -1;
            for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab(); ++nTab)
            {
                for (SCCOL nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); ++nCol)
                {
                    sc::ColumnBlockPosition hint;
                    m_pDocument->InitColumnBlockPosition( hint, nTab, nCol );
                    for (SCROW nRow = aRange.aStart.Row(); nRow <= aRange.aEnd.Row(); ++nRow)
                    {
                        if (nRow == aRange.aEnd.Row())
                        {
                            // Excel behavior: if the last row is the totals row, the data
                            // is not added to the chart. If it's not the last row, the data
                            // is added like normal.
                            const auto* pData = m_pDocument->GetDBAtCursor(
                                nCol, nRow, nTab,
                                ScDBDataPortion::AREA
                            );
                            if (pData && pData->HasTotals())
                            {
                                ScRange aTempRange;
                                pData->GetArea(aTempRange);
                                if (aTempRange.aEnd.Row() == nRow)
                                {
                                    // Current row is totals row, skip
                                    break;
                                }
                            }
                        }
                        bool bColHidden = m_pDocument->ColHidden(nCol, nTab, nullptr, &nLastCol);
                        bool bRowHidden = m_pDocument->RowHidden(nRow, nTab, nullptr, &nLastRow);
 
                        if (bColHidden || bRowHidden)
                        {
                            // hidden cell
                            aHiddenValues.push_back(nDataCount-1);
 
                            if( !m_bIncludeHiddenCells )
                                continue;
                        }
 
                        Item aItem;
 
                        ScAddress aAdr(nCol, nRow, nTab);
                        aItem.maString = m_pDocument->GetString(aAdr);
 
                        ScRefCellValue aCell(*m_pDocument, aAdr, hint);
                        switch (aCell.getType())
                        {
                            case CELLTYPE_VALUE:
                                aItem.mfValue = aCell.getValue();
                                aItem.mbIsValue = true;
                            break;
                            case CELLTYPE_FORMULA:
                            {
                                ScFormulaCell* pFCell = aCell.getFormula();
                                FormulaError nErr = pFCell->GetErrCode();
                                if (nErr != FormulaError::NONE)
                                    break;
 
                                if (pFCell->IsValue())
                                {
                                    aItem.mfValue = pFCell->GetValue();
                                    aItem.mbIsValue = true;
                                }
                            }
                            break;
                            case CELLTYPE_EDIT:
                            case CELLTYPE_NONE:
                            case CELLTYPE_STRING:
                            default:
                                ; // do nothing
                        }
 
                        aItem.mAddress = ScAddress(nCol, nRow, nTab);
 
                        m_xDataArray->push_back(std::move(aItem));
                        ++nDataCount;
                    }
                }
            }
        }
    }
 
    // convert the hidden cell list to sequence.
    m_aHiddenValues.realloc(aHiddenValues.size());
    std::copy(
        aHiddenValues.begin(), aHiddenValues.end(), m_aHiddenValues.getArray());
 
    // Clear the data series cache when the array is re-built.
    m_aMixedDataCache.realloc(0);
}
 
void ScChart2DataSequence::RebuildDataCache()
{
    if (!m_bExtDataRebuildQueued)
    {
        m_xDataArray.reset(new std::vector<Item>);
        m_pDocument->BroadcastUno(ScHint(SfxHintId::ScDataChanged, ScAddress()));
        m_bExtDataRebuildQueued = true;
        m_bGotDataChangedHint = true;
    }
}
 
sal_Int32 ScChart2DataSequence::FillCacheFromExternalRef(const ScTokenRef& pToken)
{
    ScExternalRefManager* pRefMgr = m_pDocument->GetExternalRefManager();
    ScRange aRange;
    if (!ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, pToken, ScAddress(), true))
        return 0;
 
    sal_uInt16 nFileId = pToken->GetIndex();
    OUString aTabName = pToken->GetString().getString();
    ScExternalRefCache::TokenArrayRef pArray = pRefMgr->getDoubleRefTokens(nFileId, aTabName, aRange, nullptr);
    if (!pArray)
        // no external data exists for this range.
        return 0;
 
    // Start listening for this external document.
    ExternalRefListener* pExtRefListener = GetExtRefListener();
    pRefMgr->addLinkListener(nFileId, pExtRefListener);
    pExtRefListener->addFileId(nFileId);
 
    m_xDataArray.reset(new std::vector<Item>);
    ScExternalRefCache::TableTypeRef pTable = pRefMgr->getCacheTable(nFileId, aTabName, false);
    sal_Int32 nDataCount = 0;
    FormulaTokenArrayPlainIterator aIter(*pArray);
    for (FormulaToken* p = aIter.First(); p; p = aIter.Next())
    {
        // Cached external range is always represented as a single
        // matrix token, although that might change in the future when
        // we introduce a new token type to store multi-table range
        // data.
 
        if (p->GetType() != svMatrix)
        {
            OSL_FAIL("Cached array is not a matrix token.");
            continue;
        }
 
        const ScMatrix* pMat = p->GetMatrix();
        SCSIZE nCSize, nRSize;
        pMat->GetDimensions(nCSize, nRSize);
        for (SCSIZE nC = 0; nC < nCSize; ++nC)
        {
            for (SCSIZE nR = 0; nR < nRSize; ++nR)
            {
                if (pMat->IsValue(nC, nR) || pMat->IsBoolean(nC, nR))
                {
                    Item aItem;
 
                    aItem.mbIsValue = true;
                    aItem.mfValue = pMat->GetDouble(nC, nR);
 
                    SvNumberFormatter* pFormatter = m_pDocument->GetFormatTable();
                    if (pFormatter)
                    {
                        const double fVal = aItem.mfValue;
                        const Color* pColor = nullptr;
                        sal_uInt32 nFmt = 0;
                        if (pTable)
                        {
                            // Get the correct format index from the cache.
                            SCCOL nCol = aRange.aStart.Col() + static_cast<SCCOL>(nC);
                            SCROW nRow = aRange.aStart.Row() + static_cast<SCROW>(nR);
                            pTable->getCell(nCol, nRow, &nFmt);
                        }
                        pFormatter->GetOutputString(fVal, nFmt, aItem.maString, &pColor);
                    }
 
                    m_xDataArray->push_back(std::move(aItem));
                    ++nDataCount;
                }
                else if (pMat->IsStringOrEmpty(nC, nR))
                {
                    Item aItem;
 
                    aItem.mbIsValue = false;
                    aItem.maString = pMat->GetString(nC, nR).getString();
 
                    m_xDataArray->push_back(std::move(aItem));
                    ++nDataCount;
                }
            }
        }
    }
    return nDataCount;
}
 
void ScChart2DataSequence::UpdateTokensFromRanges(const ScRangeList& rRanges)
{
    if (!m_oRangeIndices)
        return;
 
    for ( size_t i = 0, nCount = rRanges.size(); i < nCount; ++i )
    {
        ScTokenRef pToken;
        const ScRange & rRange = rRanges[i];
 
        ScRefTokenHelper::getTokenFromRange(m_pDocument, pToken, rRange);
        sal_uInt32 nOrigPos = (*m_oRangeIndices)[i];
        m_aTokens[nOrigPos] = std::move(pToken);
    }
 
    RefChanged();
 
    // any change of the range address is broadcast to value (modify) listeners
    if ( !m_aValueListeners.empty() )
        m_bGotDataChangedHint = true;
}
 
ScChart2DataSequence::ExternalRefListener* ScChart2DataSequence::GetExtRefListener()
{
    if (!m_pExtRefListener)
        m_pExtRefListener.reset(new ExternalRefListener(*this, m_pDocument));
 
    return m_pExtRefListener.get();
}
 
void ScChart2DataSequence::StopListeningToAllExternalRefs()
{
    if (!m_pExtRefListener)
        return;
 
    const std::unordered_set<sal_uInt16>& rFileIds = m_pExtRefListener->getAllFileIds();
    ScExternalRefManager* pRefMgr = m_pDocument->GetExternalRefManager();
    for (const auto& rFileId : rFileIds)
        pRefMgr->removeLinkListener(rFileId, m_pExtRefListener.get());
 
    m_pExtRefListener.reset();
}
 
 
void ScChart2DataSequence::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
{
    if ( rHint.GetId() == SfxHintId::ScUpdateRef )
    {
        // Create a range list from the token list, have the range list
        // updated, and bring the change back to the token list.
 
        ScRangeList aRanges;
        m_oRangeIndices.emplace();
        vector<ScTokenRef>::const_iterator itrBeg = m_aTokens.begin(), itrEnd = m_aTokens.end();
        for (vector<ScTokenRef>::const_iterator itr = itrBeg ;itr != itrEnd; ++itr)
        {
            if (!ScRefTokenHelper::isExternalRef(*itr))
            {
                ScRange aRange;
                ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, *itr, ScAddress());
                aRanges.push_back(aRange);
                sal_uInt32 nPos = distance(itrBeg, itr);
                m_oRangeIndices->push_back(nPos);
            }
        }
 
        assert(m_oRangeIndices->size() == aRanges.size() &&
                   "range list and range index list have different sizes.");
 
        unique_ptr<ScRangeList> pUndoRanges;
        if ( m_pDocument->HasUnoRefUndo() )
            pUndoRanges.reset(new ScRangeList(aRanges));
 
        const ScUpdateRefHint& rRef = static_cast<const ScUpdateRefHint&>(rHint);
        bool bChanged = aRanges.UpdateReference(
            rRef.GetMode(), m_pDocument, rRef.GetRange(), rRef.GetDx(), rRef.GetDy(), rRef.GetDz());
 
        if (bChanged)
        {
            // TODO: This should be an assert, but tdf#144537 triggers it.
            SAL_WARN_IF(m_oRangeIndices->size() == aRanges.size(),
                        "sc.ui", "range list and range index list have different sizes after the reference update.");
 
            // Bring the change back from the range list to the token list.
            UpdateTokensFromRanges(aRanges);
 
            if (pUndoRanges)
                m_pDocument->AddUnoRefChange(m_nObjectId, *pUndoRanges);
        }
    }
    else if ( rHint.GetId() == SfxHintId::ScUnoRefUndo )
    {
        auto pUndoHint = static_cast<const ScUnoRefUndoHint*>(&rHint);
        do
        {
            if (pUndoHint->GetObjectId() != m_nObjectId)
                break;
 
            // The hint object provides the old ranges.  Restore the old state
            // from these ranges.
 
            if (!m_oRangeIndices || m_oRangeIndices->empty())
            {
                assert(false && " faulty range indices");
                break;
            }
 
            const ScRangeList& rRanges = pUndoHint->GetRanges();
 
            size_t nCount = rRanges.size();
            if (nCount != m_oRangeIndices->size())
            {
                assert(false && "range count and range index count differ.");
                break;
            }
 
            UpdateTokensFromRanges(rRanges);
        }
        while (false);
    }
    else
    {
        const SfxHintId nId = rHint.GetId();
        if ( nId ==SfxHintId::Dying )
        {
            m_pDocument = nullptr;
        }
        else if ( nId == SfxHintId::DataChanged )
        {
            // delayed broadcast as in ScCellRangesBase
 
            if ( m_bGotDataChangedHint && m_pDocument )
            {
                m_xDataArray.reset(new std::vector<Item>);
                lang::EventObject aEvent;
                aEvent.Source = getXWeak();
 
                if( m_pDocument )
                {
                    for (const uno::Reference<util::XModifyListener> & xListener: m_aValueListeners)
                        m_pDocument->AddUnoListenerCall( xListener, aEvent );
                }
 
                m_bGotDataChangedHint = false;
            }
        }
        else if ( nId == SfxHintId::ScCalcAll )
        {
            // broadcast from DoHardRecalc - set m_bGotDataChangedHint
            // (SfxHintId::DataChanged follows separately)
 
            if ( !m_aValueListeners.empty() )
                m_bGotDataChangedHint = true;
        }
        else if (nId == SfxHintId::ScClearCache)
        {
            // necessary after import
            m_xDataArray.reset(new std::vector<Item>);
        }
    }
}
 
IMPL_LINK( ScChart2DataSequence, ValueListenerHdl, const SfxHint&, rHint, void )
{
    if ( m_pDocument && (rHint.GetId() == SfxHintId::ScDataChanged) )
    {
        //  This may be called several times for a single change, if several formulas
        //  in the range are notified. So only a flag is set that is checked when
        //  SfxHintId::DataChanged is received.
 
        setDataChangedHint(true);
    }
}
 
ScChart2DataSequence::ExternalRefListener::ExternalRefListener(
    ScChart2DataSequence& rParent, ScDocument* pDoc) :
    mrParent(rParent),
    mpDoc(pDoc)
{
}
 
ScChart2DataSequence::ExternalRefListener::~ExternalRefListener()
{
    if (!mpDoc || mpDoc->IsInDtorClear())
        // The document is being destroyed.  Do nothing.
        return;
 
    // Make sure to remove all pointers to this object.
    mpDoc->GetExternalRefManager()->removeLinkListener(this);
}
 
void ScChart2DataSequence::ExternalRefListener::notify(sal_uInt16 nFileId, ScExternalRefManager::LinkUpdateType eType)
{
    switch (eType)
    {
        case ScExternalRefManager::LINK_MODIFIED:
        {
            if (maFileIds.count(nFileId))
                // We are listening to this external document.
                mrParent.RebuildDataCache();
        }
        break;
        case ScExternalRefManager::LINK_BROKEN:
            maFileIds.erase(nFileId);
        break;
        case ScExternalRefManager::OH_NO_WE_ARE_GOING_TO_DIE:
            mpDoc = nullptr;
        break;
    }
}
 
void ScChart2DataSequence::ExternalRefListener::addFileId(sal_uInt16 nFileId)
{
    maFileIds.insert(nFileId);
}
 
uno::Sequence< uno::Any> SAL_CALL ScChart2DataSequence::getData()
{
    SolarMutexGuard aGuard;
    if ( !m_pDocument)
        throw uno::RuntimeException();
 
    BuildDataCache();
 
    if (!m_aMixedDataCache.hasElements())
    {
        // Build a cache for the 1st time...
 
        sal_Int32 nCount = m_xDataArray->size();
        m_aMixedDataCache.realloc(nCount);
        uno::Any* pArr = m_aMixedDataCache.getArray();
        for (const Item &rItem : *m_xDataArray)
        {
            if (rItem.mbIsValue)
                *pArr <<= rItem.mfValue;
            else if (rItem.maString.isEmpty())
            {
                ScRefCellValue aCell(*m_pDocument, rItem.mAddress);
                if (aCell.isEmpty())
                   *pArr = uno::Any();
                else
                   *pArr <<= rItem.maString;
            }
            else
                *pArr <<= rItem.maString;
            ++pArr;
        }
    }
    return m_aMixedDataCache;
}
 
// XNumericalDataSequence --------------------------------------------------
 
uno::Sequence< double > SAL_CALL ScChart2DataSequence::getNumericalData()
{
    SolarMutexGuard aGuard;
    if ( !m_pDocument)
        throw uno::RuntimeException();
 
    BuildDataCache();
 
    sal_Int32 nCount = m_xDataArray->size();
    uno::Sequence<double> aSeq(nCount);
    double* pArr = aSeq.getArray();
    for (const Item& rItem : *m_xDataArray)
    {
        *pArr = rItem.mbIsValue ? rItem.mfValue : std::numeric_limits<double>::quiet_NaN();
        ++pArr;
    }
 
    return aSeq;
}
 
// XTextualDataSequence --------------------------------------------------
 
uno::Sequence< OUString > SAL_CALL ScChart2DataSequence::getTextualData()
{
    SolarMutexGuard aGuard;
    uno::Sequence<OUString> aSeq;
    if ( !m_pDocument )
        throw uno::RuntimeException();
 
    BuildDataCache();
 
    sal_Int32 nCount = m_xDataArray->size();
    if ( nCount > 0 )
    {
        aSeq =  uno::Sequence<OUString>(nCount);
        OUString* pArr = aSeq.getArray();
        for (const Item& rItem : *m_xDataArray)
        {
            *pArr = rItem.maString;
            ++pArr;
        }
    }
    else if ( m_aTokens.front() )
    {
        if( m_aTokens.front()->GetType() == svString )
        {
            aSeq = uno::Sequence<OUString> { m_aTokens.front()->GetString().getString() };
        }
    }
 
    return aSeq;
}
 
OUString SAL_CALL ScChart2DataSequence::getSourceRangeRepresentation()
{
    SolarMutexGuard aGuard;
    OUString aStr;
    OSL_ENSURE( m_pDocument, "No Document -> no SourceRangeRepresentation" );
    if (m_pDocument)
        lcl_convertTokensToString(aStr, m_aTokens, *m_pDocument);
 
    return aStr;
}
 
namespace {
 
/**
 * This function object is used to accumulatively count the numbers of
 * columns and rows in all reference tokens.
 */
class AccumulateRangeSize
{
public:
    AccumulateRangeSize(const ScDocument* pDoc) :
        mpDoc(pDoc), mnCols(0), mnRows(0) {}
 
    void operator() (const ScTokenRef& pToken)
    {
        ScRange r;
        bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
        ScRefTokenHelper::getRangeFromToken(mpDoc, r, pToken, ScAddress(), bExternal);
        r.PutInOrder();
        mnCols += r.aEnd.Col() - r.aStart.Col() + 1;
        mnRows += r.aEnd.Row() - r.aStart.Row() + 1;
    }
 
    SCCOL getCols() const { return mnCols; }
    SCROW getRows() const { return mnRows; }
private:
    const ScDocument* mpDoc;
    SCCOL mnCols;
    SCROW mnRows;
};
 
/**
 * This function object is used to generate label strings from a list of
 * reference tokens.
 */
class GenerateLabelStrings
{
public:
    GenerateLabelStrings(const ScDocument* pDoc, sal_Int32 nSize, chart2::data::LabelOrigin eOrigin, bool bColumn) :
        mpDoc(pDoc),
        mpLabels(std::make_shared<Sequence<OUString>>(nSize)),
        meOrigin(eOrigin),
        mnCount(0),
        mbColumn(bColumn) {}
 
    void operator() (const ScTokenRef& pToken)
    {
        bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
        ScRange aRange;
        ScRefTokenHelper::getRangeFromToken(mpDoc, aRange, pToken, ScAddress(), bExternal);
        OUString* pArr = mpLabels->getArray();
        if (mbColumn)
        {
            for (SCCOL nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); ++nCol)
            {
                if ( meOrigin != chart2::data::LabelOrigin_LONG_SIDE)
                {
                    OUString aString = ScResId(STR_COLUMN) + " ";
                    ScAddress aPos( nCol, 0, 0 );
                    OUString aColStr(aPos.Format(ScRefFlags::COL_VALID));
                    aString += aColStr;
                    pArr[mnCount] = aString;
                }
                else //only indices for categories
                    pArr[mnCount] = OUString::number( mnCount+1 );
                ++mnCount;
            }
        }
        else
        {
            for (sal_Int32 nRow = aRange.aStart.Row(); nRow <= aRange.aEnd.Row(); ++nRow)
            {
                if (meOrigin != chart2::data::LabelOrigin_LONG_SIDE)
                    pArr[mnCount] = ScResId(STR_ROW) + " " + OUString::number(nRow + 1);
                else //only indices for categories
                    pArr[mnCount] = OUString::number( mnCount+1 );
                ++mnCount;
            }
        }
    }
 
    const Sequence<OUString>& getLabels() const { return *mpLabels; }
 
private:
    const ScDocument*                   mpDoc;
    shared_ptr< Sequence<OUString> >    mpLabels;
    chart2::data::LabelOrigin           meOrigin;
    sal_Int32                           mnCount;
    bool                                mbColumn;
};
 
}
 
uno::Sequence< OUString > SAL_CALL ScChart2DataSequence::generateLabel(chart2::data::LabelOrigin eOrigin)
{
    SolarMutexGuard aGuard;
    if ( !m_pDocument)
        throw uno::RuntimeException();
 
    // Determine the total size of all ranges.
    AccumulateRangeSize func(m_pDocument);
    func = ::std::for_each(m_aTokens.begin(), m_aTokens.end(), func);
    SCCOL nCols = func.getCols();
    SCROW nRows = func.getRows();
 
    // Determine whether this is column-major or row-major.
    bool bColumn = true;
    if ((eOrigin == chart2::data::LabelOrigin_SHORT_SIDE) ||
        (eOrigin == chart2::data::LabelOrigin_LONG_SIDE))
    {
        if (nRows > nCols)
        {
            bColumn = eOrigin == chart2::data::LabelOrigin_SHORT_SIDE;
        }
        else if (nCols > nRows)
        {
            bColumn = eOrigin != chart2::data::LabelOrigin_SHORT_SIDE;
        }
        else
            return Sequence<OUString>();
    }
 
    // Generate label strings based on the info so far.
    sal_Int32 nCount = bColumn ? nCols : nRows;
    GenerateLabelStrings genLabels(m_pDocument, nCount, eOrigin, bColumn);
    genLabels = ::std::for_each(m_aTokens.begin(), m_aTokens.end(), genLabels);
    Sequence<OUString> aSeq = genLabels.getLabels();
 
    return aSeq;
}
 
namespace {
 
sal_uInt32 getDisplayNumberFormat(const ScDocument* pDoc, const ScAddress& rPos)
{
    sal_uInt32 nFormat = pDoc->GetNumberFormat(ScRange(rPos)); // original format from cell.
    return nFormat;
}
 
}
 
::sal_Int32 SAL_CALL ScChart2DataSequence::getNumberFormatKeyByIndex( ::sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    BuildDataCache();
 
    if (nIndex == -1)
    {
        // return format of first non-empty cell
        // TODO: use nicer heuristic
        for (const Item& rItem : *m_xDataArray)
        {
            ScRefCellValue aCell(*m_pDocument, rItem.mAddress);
            if (!aCell.isEmpty() && aCell.hasNumeric())
            {
                return static_cast<sal_Int32>(getDisplayNumberFormat(m_pDocument, rItem.mAddress));
            }
        }
 
        // we could not find a non-empty cell
        return 0;
    }
 
    if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= m_xDataArray->size())
    {
        SAL_WARN("sc.ui", "Passed invalid index to getNumberFormatKeyByIndex(). Will return default value '0'.");
        return 0;
    }
 
    return static_cast<sal_Int32>(getDisplayNumberFormat(m_pDocument, m_xDataArray->at(nIndex).mAddress));
}
 
// XCloneable ================================================================
 
uno::Reference< util::XCloneable > SAL_CALL ScChart2DataSequence::createClone()
{
    SolarMutexGuard aGuard;
 
    rtl::Reference<ScChart2DataSequence> p(new ScChart2DataSequence(m_pDocument, *this));
    return p;
}
 
// XModifyBroadcaster ========================================================
 
void SAL_CALL ScChart2DataSequence::addModifyListener( const uno::Reference< util::XModifyListener >& aListener )
{
    // like ScCellRangesBase::addModifyListener
    SolarMutexGuard aGuard;
    if (m_aTokens.empty())
        return;
 
    ScRangeList aRanges;
    ScRefTokenHelper::getRangeListFromTokens(m_pDocument, aRanges, m_aTokens, ScAddress());
    m_aValueListeners.emplace_back( aListener );
 
    if ( m_aValueListeners.size() != 1 )
        return;
 
    if (!m_pValueListener)
        m_pValueListener.reset(new ScLinkListener( LINK( this, ScChart2DataSequence, ValueListenerHdl ) ));
 
    if (!m_pHiddenListener)
        m_pHiddenListener.reset(new HiddenRangeListener(*this));
 
    if( m_pDocument )
    {
        ScChartListenerCollection* pCLC = m_pDocument->GetChartListenerCollection();
        for (const auto& rxToken : m_aTokens)
        {
            ScRange aRange;
            if (!ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, rxToken, ScAddress()))
                continue;
 
            m_pDocument->StartListeningArea( aRange, false, m_pValueListener.get() );
            if (pCLC)
                pCLC->StartListeningHiddenRange(aRange, m_pHiddenListener.get());
        }
    }
 
    acquire();  // don't lose this object (one ref for all listeners)
}
 
void SAL_CALL ScChart2DataSequence::removeModifyListener( const uno::Reference< util::XModifyListener >& aListener )
{
    // like ScCellRangesBase::removeModifyListener
 
    SolarMutexGuard aGuard;
    if (m_aTokens.empty())
        return;
 
    rtl::Reference<ScChart2DataSequence> xSelfHold(this);      // in case the listeners have the last ref
 
    sal_uInt16 nCount = m_aValueListeners.size();
    for ( sal_uInt16 n=nCount; n--; )
    {
        uno::Reference<util::XModifyListener>& rObj = m_aValueListeners[n];
        if ( rObj == aListener )
        {
            m_aValueListeners.erase( m_aValueListeners.begin() + n );
 
            if ( m_aValueListeners.empty() )
            {
                if (m_pValueListener)
                    m_pValueListener->EndListeningAll();
 
                if (m_pHiddenListener && m_pDocument)
                {
                    ScChartListenerCollection* pCLC = m_pDocument->GetChartListenerCollection();
                    if (pCLC)
                        pCLC->EndListeningHiddenRange(m_pHiddenListener.get());
                }
 
                release();      // release the ref for the listeners
            }
 
            break;
        }
    }
}
 
// DataSequence XPropertySet -------------------------------------------------
 
uno::Reference< beans::XPropertySetInfo> SAL_CALL
ScChart2DataSequence::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
    static uno::Reference<beans::XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( m_aPropSet.getPropertyMap() );
    return aRef;
}
 
void SAL_CALL ScChart2DataSequence::setPropertyValue(
        const OUString& rPropertyName, const uno::Any& rValue)
{
    if ( rPropertyName == SC_UNONAME_ROLE )
    {
        if ( !(rValue >>= m_aRole))
            throw lang::IllegalArgumentException();
    }
    else if ( rPropertyName == SC_UNONAME_INCLUDEHIDDENCELLS )
    {
        bool bOldValue = m_bIncludeHiddenCells;
        if ( !(rValue >>= m_bIncludeHiddenCells))
            throw lang::IllegalArgumentException();
        if( bOldValue != m_bIncludeHiddenCells )
            m_xDataArray.reset(new std::vector<Item>);//data array is dirty now
    }
    else if( rPropertyName == "TimeBased" )
    {
        bool bTimeBased = mbTimeBased;
        rValue>>= bTimeBased;
        mbTimeBased = bTimeBased;
    }
    else
        throw beans::UnknownPropertyException(rPropertyName);
    // TODO: support optional properties
}
 
uno::Any SAL_CALL ScChart2DataSequence::getPropertyValue(const OUString& rPropertyName)
{
    uno::Any aRet;
    if ( rPropertyName == SC_UNONAME_ROLE )
        aRet <<= m_aRole;
    else if ( rPropertyName == SC_UNONAME_INCLUDEHIDDENCELLS )
        aRet <<= m_bIncludeHiddenCells;
    else if ( rPropertyName == SC_UNONAME_HIDDENVALUES )
    {
        // This property is read-only thus cannot be set externally via
        // setPropertyValue(...).
        BuildDataCache();
        aRet <<= m_aHiddenValues;
    }
    else if (rPropertyName == SC_UNONAME_TIME_BASED)
    {
        aRet <<= mbTimeBased;
    }
    else if (rPropertyName == SC_UNONAME_HAS_STRING_LABEL)
    {
        // Read-only property.  It returns whether or not the label value is a
        // direct user input, rather than an indirect reference.
        bool bHasStringLabel = false;
        if (m_aTokens.size() == 1)
        {
            const formula::FormulaToken& rToken = *m_aTokens[0];
            bHasStringLabel = rToken.GetType() == formula::svString;
        }
        aRet <<= bHasStringLabel;
    }
    else
        throw beans::UnknownPropertyException(rPropertyName);
    // TODO: support optional properties
    return aRet;
}
 
void SAL_CALL ScChart2DataSequence::addPropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XPropertyChangeListener>& /*xListener*/)
{
    // FIXME: real implementation
    OSL_FAIL( "Not yet implemented" );
}
 
void SAL_CALL ScChart2DataSequence::removePropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XPropertyChangeListener>& /*rListener*/)
{
    // FIXME: real implementation
    OSL_FAIL( "Not yet implemented" );
}
 
void SAL_CALL ScChart2DataSequence::addVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XVetoableChangeListener>& /*rListener*/)
{
    // FIXME: real implementation
    OSL_FAIL( "Not yet implemented" );
}
 
void SAL_CALL ScChart2DataSequence::removeVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XVetoableChangeListener>& /*rListener*/)
{
    // FIXME: real implementation
    OSL_FAIL( "Not yet implemented" );
}
 
void ScChart2DataSequence::setDataChangedHint(bool b)
{
    m_bGotDataChangedHint = b;
}
 
sal_Bool ScChart2DataSequence::switchToNext(sal_Bool bWrap)
{
    if(!mbTimeBased)
        return true;
 
    if(mnCurrentTab >= mnTimeBasedEnd)
    {
        if(bWrap)
            setToPointInTime(0);
        return false;
    }
 
    for(const auto& rxToken : m_aTokens)
    {
        if (rxToken->GetType() != svDoubleRef)
            continue;
 
        ScComplexRefData& rData = *rxToken->GetDoubleRef();
        ScSingleRefData& s = rData.Ref1;
        ScSingleRefData& e = rData.Ref2;
 
        s.IncTab(1);
        e.IncTab(1);
    }
 
    ++mnCurrentTab;
 
    RebuildDataCache();
 
    return true;
}
 
void ScChart2DataSequence::setRange(sal_Int32 nStart, sal_Int32 nEnd)
{
    mnTimeBasedStart = nStart;
    mnTimeBasedEnd = nEnd;
    mnCurrentTab = mnTimeBasedStart;
}
 
sal_Bool ScChart2DataSequence::setToPointInTime(sal_Int32 nPoint)
{
    if(nPoint > mnTimeBasedEnd - mnTimeBasedStart)
        return false;
 
    SCTAB nTab = mnTimeBasedStart + nPoint;
    for(const auto& rxToken : m_aTokens)
    {
        if (rxToken->GetType() != svDoubleRef)
            continue;
 
        ScComplexRefData& rData = *rxToken->GetDoubleRef();
        ScSingleRefData& s = rData.Ref1;
        ScSingleRefData& e = rData.Ref2;
 
        s.SetAbsTab(nTab);
        e.SetAbsTab(nTab);
    }
 
    mnCurrentTab = nTab;
 
    RebuildDataCache();
 
    return true;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V547 Expression 'bTimeBased' is always false.

V547 Expression '!bTimeBased' is always true.

V547 Expression 'm_pDocument' is always true.

V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.