Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  chart2uno.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <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();

        for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
        {
            assert (nCol1 >= 0);
            sal_uInt32 nInsCol = bNoGlue ? 0 : static_cast<sal_uInt32>(nCol1) & 0xFFFF;

            for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol, ++nInsCol)
            {
                // 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 nInsKey = (static_cast<sal_uInt32>(nTab) << 16) | nInsCol;
                FormulaTokenMap& rCol = aCols[nInsKey];

                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 );
            }
        }
        catchconst 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>&&nbsp;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;
        forconst 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 ) ),
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=93 G=93

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge