Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sw/source/core/unocore/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 96 kB image not shown  

Quelle  unochart.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 <algorithm>
#include <string_view>

#include <com/sun/star/chart/ChartDataRowSource.hpp>
#include <com/sun/star/chart2/data/LabelOrigin.hpp>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <comphelper/diagnose_ex.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <o3tl/deleter.hxx>
#include <o3tl/string_view.hxx>
#include <mutex>
#include <vcl/svapp.hxx>

#include "XMLRangeHelper.hxx"
#include <unochart.hxx>
#include <swtable.hxx>
#include <unoprnms.hxx>
#include <unomap.hxx>
#include <unocrsr.hxx>
#include <unotbl.hxx>
#include <doc.hxx>
#include <IDocumentChartDataProviderAccess.hxx>
#include <frmfmt.hxx>
#include <ndole.hxx>
#include <swtypes.hxx>
#include <strings.hrc>
#include <comphelper/servicehelper.hxx>
#include <comphelper/string.hxx>
#include <svl/itemprop.hxx>

using namespace ::com::sun::star;

void SwChartHelper::DoUpdateAllCharts( SwDoc* pDoc )
{
    if (!pDoc)
        return;

    SwOLENode *pONd;
    SwStartNode *pStNd;
    SwNodeIndex aIdx( *pDoc->GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 );
    while( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) )
    {
        ++aIdx;
        if (nullptr != ( pONd = aIdx.GetNode().GetOLENode() ) &&
            pONd->GetOLEObj().GetObject().IsChart() )
        {
            // Load the object and set modified

            uno::Reference < embed::XEmbeddedObject > xIP = pONd->GetOLEObj().GetOleRef();
            if ( svt::EmbeddedObjectRef::TryRunningState( xIP ) )
            {
                try
                {
                    uno::Reference< util::XModifiable > xModif( xIP->getComponent(), uno::UNO_QUERY );
                    if (xModif)
                        xModif->setModified( true );
                }
                catch ( uno::Exception& )
                {
                }

            }
        }
        aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 );
    }
}

SwChartLockController_Helper::SwChartLockController_Helper( SwDoc *pDocument ) :
    m_pDoc( pDocument )
    , m_aUnlockTimer( "sw::SwChartLockController_Helper aUnlockTimer" )
    , m_bIsLocked( false )
{
    m_aUnlockTimer.SetTimeout( 1500 );
    m_aUnlockTimer.SetInvokeHandler( LINK( this, SwChartLockController_Helper, DoUnlockAllCharts ));
}

SwChartLockController_Helper::~SwChartLockController_Helper()
{
    if (m_pDoc)   // still connected?
        suppress_fun_call_w_exception(Disconnect());
}

void SwChartLockController_Helper::StartOrContinueLocking()
{
    if (!m_bIsLocked)
        LockAllCharts();
    m_aUnlockTimer.Start();   // start or continue time of locking
}

void SwChartLockController_Helper::Disconnect()
{
    m_aUnlockTimer.Stop();
    UnlockAllCharts();
    m_pDoc = nullptr;
}

void SwChartLockController_Helper::LockUnlockAllCharts( bool bLock )
{
    if (!m_pDoc)
        return;

    uno::Reference< frame::XModel > xRes;
    SwOLENode *pONd;
    SwStartNode *pStNd;
    SwNodeIndex aIdx( *m_pDoc->GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 );
    while( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) )
    {
        ++aIdx;
        if (nullptr != ( pONd = aIdx.GetNode().GetOLENode() ) &&
            !pONd->GetChartTableName().isEmpty() /* is chart object? */)
        {
            uno::Reference < embed::XEmbeddedObject > xIP = pONd->GetOLEObj().GetOleRef();
            if ( svt::EmbeddedObjectRef::TryRunningState( xIP ) )
            {
                xRes.set( xIP->getComponent(), uno::UNO_QUERY );
                if (xRes.is())
                {
                    if (bLock)
                        xRes->lockControllers();
                    else
                        xRes->unlockControllers();
                }
            }
        }
        aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 );
    }

    m_bIsLocked = bLock;
}

IMPL_LINK_NOARG( SwChartLockController_Helper, DoUnlockAllCharts, Timer *, void )
{
    UnlockAllCharts();
}

static std::mutex &    GetChartMutex()
{
    static std::mutex aMutex;
    return aMutex;
}

static void LaunchModifiedEvent(
        const ::comphelper::OInterfaceContainerHelper4<util::XModifyListener> &rICH,
        const uno::Reference< uno::XInterface > &rxI )
{
    lang::EventObject aEvtObj( rxI );
    std::unique_lock aGuard(GetChartMutex());
    rICH.notifyEach( aGuard, &util::XModifyListener::modified, aEvtObj );
}

/**
 * rCellRangeName needs to be of one of the following formats:
 * - e.g. "A2:E5" or
 *   - e.g. "Table1.A2:E5"
 */

bool FillRangeDescriptor(
        SwRangeDescriptor &rDesc,
        std::u16string_view rCellRangeName )
{
    sal_Int32 nToken = std::u16string_view::npos == rCellRangeName.find('.') ? 0 : 1;
    std::u16string_view aCellRangeNoTableName( o3tl::getToken(rCellRangeName, nToken, '.' ) );
    OUString aTLName( o3tl::getToken(aCellRangeNoTableName, 0, ':') );  // name of top left cell
    OUString aBRName( o3tl::getToken(aCellRangeNoTableName, 1, ':') );  // name of bottom right cell
    if(aTLName.isEmpty() || aBRName.isEmpty())
        return false;

    rDesc.nTop = rDesc.nLeft = rDesc.nBottom = rDesc.nRight = -1;
    SwXTextTable::GetCellPosition( aTLName, rDesc.nLeft,  rDesc.nTop );
    SwXTextTable::GetCellPosition( aBRName, rDesc.nRight, rDesc.nBottom );
    rDesc.Normalize();
    OSL_ENSURE( rDesc.nTop    != -1 &&
                rDesc.nLeft   != -1 &&
                rDesc.nBottom != -1 &&
                rDesc.nRight  != -1,
            "failed to get range descriptor" );
    OSL_ENSURE( rDesc.nTop <= rDesc.nBottom  &&  rDesc.nLeft <= rDesc.nRight,
            "invalid range descriptor");
    return true;
}

static OUString GetCellRangeName( const SwFrameFormat &rTableFormat, SwUnoCursor &rTableCursor )
{
    OUString aRes;

    //!! see also SwXTextTableCursor::getRangeName

    SwUnoTableCursor* pUnoTableCursor = dynamic_cast<SwUnoTableCursor*>(&rTableCursor);
    if (!pUnoTableCursor)
        return OUString();

    // tdf#132714 empty outdated selection cache to avoid crashing in ActualizeSelection()
    size_t nCount = pUnoTableCursor->GetSelectedBoxesCount();
    while (nCount--)
        pUnoTableCursor->DeleteBox(nCount);

    pUnoTableCursor->MakeBoxSels();

    const SwStartNode*  pStart;
    const SwTableBox*   pStartBox   = nullptr;
    const SwTableBox*   pEndBox     = nullptr;

    pStart = pUnoTableCursor->GetPoint()->GetNode().FindTableBoxStartNode();
    if (pStart)
    {
        const SwTable* pTable = SwTable::FindTable( &rTableFormat );
        pEndBox = pTable->GetTableBox( pStart->GetIndex());
        aRes = pEndBox->GetName();

        if(pUnoTableCursor->HasMark())
        {
            pStart = pUnoTableCursor->GetMark()->GetNode().FindTableBoxStartNode();
            pStartBox = pTable->GetTableBox( pStart->GetIndex());
        }
        OSL_ENSURE( pStartBox, "start box not found" );
        OSL_ENSURE( pEndBox, "end box not found" );

        // need to switch start and end?
        if (*pUnoTableCursor->GetPoint() < *pUnoTableCursor->GetMark())
        {
            const SwTableBox* pTmpBox = pStartBox;
            pStartBox = pEndBox;
            pEndBox = pTmpBox;
        }

        if (!pStartBox)
            return aRes;

        aRes = pStartBox->GetName() + ":";
        if (pEndBox)
            aRes += pEndBox->GetName();
        else
            aRes += pStartBox->GetName();
    }

    return aRes;
}

static OUString GetRangeRepFromTableAndCells( const UIName& rTableName,
        std::u16string_view rStartCell, std::u16string_view rEndCell,
        bool bForceEndCellName )
{
    OSL_ENSURE( !rTableName.isEmpty(), "table name missing" );
    OSL_ENSURE( !rStartCell.empty(), "cell name missing" );
    OUString aRes = rTableName.toString() + "." + rStartCell;

    if (!rEndCell.empty())
    {
        aRes += OUString::Concat(":") + rEndCell;
    }
    else if (bForceEndCellName)
    {
        aRes += OUString::Concat(":") + rStartCell;
    }

    return aRes;
}

static bool GetTableAndCellsFromRangeRep(
        std::u16string_view rRangeRepresentation,
        UIName &rTableName,
        OUString &rStartCell,
        OUString &rEndCell,
        bool bSortStartEndCells = true )
{
    // parse range representation for table name and cell/range names
    // accepted format sth like: "Table1.A2:C5" , "Table2.A2.1:B3.2"
    OUString aTableName;    // table name
    OUString aStartCell;  // name of top left cell
    OUString aEndCell;    // name of bottom right cell
    size_t nIdx = rRangeRepresentation.find( '.' );
    if (nIdx != std::u16string_view::npos)
    {
        aTableName = rRangeRepresentation.substr( 0, nIdx );
        std::u16string_view aRange = rRangeRepresentation.substr( nIdx + 1 ); // cell range
        size_t nPos = aRange.find( ':' );
        if (nPos != std::u16string_view::npos) // a cell-range like "Table1.A2:D4"
        {
            aStartCell = aRange.substr( 0, nPos );
            aEndCell   = aRange.substr( nPos + 1 );

            // need to switch start and end cell ?
            // (does not check for normalization here)
            if (bSortStartEndCells && 1 == sw_CompareCellsByColFirst( aStartCell, aEndCell ))
                std::swap(aStartCell, aEndCell);
        }
        else    // a single cell like in "Table1.B3"
        {
            aStartCell = aEndCell = aRange;
        }
    }

    bool bSuccess = !aTableName.isEmpty() &&
                        !aStartCell.isEmpty() && !aEndCell.isEmpty();
    if (bSuccess)
    {
        rTableName    = UIName(aTableName);
        rStartCell  = aStartCell;
        rEndCell    = aEndCell;
    }
    return bSuccess;
}

static void GetTableByName( const SwDoc &rDoc, const UIName& rTableName,
        SwFrameFormat **ppTableFormat, SwTable **ppTable)
{
    SwFrameFormat *pTableFormat = nullptr;

    // find frame format of table
    //! see SwXTextTables::getByName
    const size_t nCount = rDoc.GetTableFrameFormatCount(true);
    for (size_t i = 0; i < nCount && !pTableFormat; ++i)
    {
        SwFrameFormat& rTableFormat = rDoc.GetTableFrameFormat(i, true);
        if(rTableName == rTableFormat.GetName())
            pTableFormat = &rTableFormat;
    }

    if (ppTableFormat)
        *ppTableFormat = pTableFormat;

    if (ppTable)
        *ppTable = pTableFormat ? SwTable::FindTable( pTableFormat ) : nullptr;
}

static void GetFormatAndCreateCursorFromRangeRep(
        const SwDoc    *pDoc,
        std::u16string_view rRangeRepresentation,   // must be a single range (i.e. so called sub-range)
        SwFrameFormat    **ppTableFormat,     // will be set to the table format of the table used in the range representation
        std::shared_ptr<SwUnoCursor>&   rpUnoCursor )   // will be set to cursor spanning the cell range (cursor will be created!)
{
    UIName aTableName;    // table name
    OUString aStartCell;  // name of top left cell
    OUString aEndCell;    // name of bottom right cell
    bool bNamesFound = GetTableAndCellsFromRangeRep( rRangeRepresentation,
                                  aTableName, aStartCell, aEndCell );

    if (!bNamesFound)
    {
        if (ppTableFormat)
            *ppTableFormat   = nullptr;
        rpUnoCursor.reset();
    }
    else
    {
        SwFrameFormat *pTableFormat = nullptr;

        // is the correct table format already provided?
        if (*ppTableFormat != nullptr  &&  (*ppTableFormat)->GetName() == aTableName)
            pTableFormat = *ppTableFormat;
        else
            GetTableByName( *pDoc, aTableName, &pTableFormat, nullptr );

        *ppTableFormat = pTableFormat;

        rpUnoCursor.reset();  // default result in case of failure

        SwTable *pTable = pTableFormat ? SwTable::FindTable( pTableFormat ) : nullptr;
        // create new SwUnoCursor spanning the specified range
        //! see also SwXTextTable::GetRangeByName
        // #i80314#
        // perform validation check. Thus, pass <true> as 2nd parameter to <SwTable::GetTableBox(..)>
        const SwTableBox* pTLBox =
                        pTable ? pTable->GetTableBox( aStartCell, true ) : nullptr;
        if(pTLBox)
        {
            const SwStartNode* pSttNd = pTLBox->GetSttNd();
            SwPosition aPos(*pSttNd);

            // set cursor to top left box of range
            auto pUnoCursor = pTableFormat->GetDoc().CreateUnoCursor(aPos, true);
            pUnoCursor->Move( fnMoveForward, GoInNode );
            pUnoCursor->SetRemainInSection( false );

            // #i80314#
            // perform validation check. Thus, pass <true> as 2nd parameter to <SwTable::GetTableBox(..)>
            const SwTableBox* pBRBox = pTable->GetTableBox( aEndCell, true );
            if(pBRBox)
            {
                pUnoCursor->SetMark();
                pUnoCursor->GetPoint()->Assign( *pBRBox->GetSttNd() );
                pUnoCursor->Move( fnMoveForward, GoInNode );
                SwUnoTableCursor& rCursor =
                    dynamic_cast<SwUnoTableCursor&>(*pUnoCursor);
                // HACK: remove pending actions for old style tables
                UnoActionRemoveContext aRemoveContext(rCursor);
                rCursor.MakeBoxSels();
                rpUnoCursor = std::move(pUnoCursor);
            }
        }
    }
}

static bool GetSubranges( std::u16string_view rRangeRepresentation,
        uno::Sequence< OUString > &rSubRanges, bool bNormalize )
{
    bool bRes = true;
    const sal_Int32 nLen = comphelper::string::getTokenCount(rRangeRepresentation, ';');
    uno::Sequence< OUString > aRanges( nLen );

    sal_Int32 nCnt = 0;
    if (nLen != 0)
    {
        OUString *pRanges = aRanges.getArray();
        UIName aFirstTable;
        sal_Int32 nPos = 0;
        for( sal_Int32 i = 0; i < nLen && bRes; ++i )
        {
            const OUString aRange( o3tl::getToken(rRangeRepresentation, 0, ';', nPos ) );
            if (!aRange.isEmpty())
            {
                pRanges[nCnt] = aRange;

                UIName aTableName;
                OUString aStartCell, aEndCell;
                if (!GetTableAndCellsFromRangeRep( aRange,
                                                   aTableName, aStartCell, aEndCell ))
                    bRes = false;

                if (bNormalize)
                {
                    sw_NormalizeRange( aStartCell, aEndCell );
                    pRanges[nCnt] = GetRangeRepFromTableAndCells( aTableName,
                                    aStartCell, aEndCell, true );
                }

                // make sure to use only a single table
                if (nCnt == 0)
                    aFirstTable = std::move(aTableName);
                else
                    if (aFirstTable != aTableName) bRes = false;

                ++nCnt;
            }
        }
    }
    aRanges.realloc( nCnt );

    rSubRanges = std::move(aRanges);
    return bRes;
}

static void SortSubranges( uno::Sequence< OUString > &rSubRanges, bool bCmpByColumn )
{
    sal_Int32 nLen = rSubRanges.getLength();
    OUString *pSubRanges = rSubRanges.getArray();

    UIName aSmallestTableName;
    OUString aSmallestStartCell;
    OUString aSmallestEndCell;

    for (sal_Int32 i = 0;  i < nLen;  ++i)
    {
        sal_Int32 nIdxOfSmallest = i;
        GetTableAndCellsFromRangeRep( pSubRanges[nIdxOfSmallest],
                aSmallestTableName, aSmallestStartCell, aSmallestEndCell );
        if (aSmallestEndCell.isEmpty())
            aSmallestEndCell = aSmallestStartCell;

        for (sal_Int32 k = i+1;  k < nLen;  ++k)
        {
            // get cell names for sub range
            UIName aTableName;
            OUString aStartCell;
            OUString aEndCell;
            GetTableAndCellsFromRangeRep( pSubRanges[k],
                    aTableName, aStartCell, aEndCell );
            if (aEndCell.isEmpty())
                aEndCell = aStartCell;

            // compare cell ranges ( is the new one smaller? )
            if (-1 == sw_CompareCellRanges( aStartCell, aEndCell,
                                aSmallestStartCell, aSmallestEndCell, bCmpByColumn ))
            {
                nIdxOfSmallest = k;
                aSmallestTableName = std::move(aTableName);
                aSmallestStartCell  = aStartCell;
                aSmallestEndCell    = aEndCell;
            }
        }

        // move smallest element to the start of the not sorted area
        const OUString aTmp( pSubRanges[ nIdxOfSmallest ] );
        pSubRanges[ nIdxOfSmallest ] = pSubRanges[ i ];
        pSubRanges[ i ] = aTmp;
    }
}

SwChartDataProvider::SwChartDataProvider( const SwDoc& rSwDoc ) :
    m_pDoc( &rSwDoc )
{
    m_bDisposed = false;
}

SwChartDataProvider::~SwChartDataProvider()
{
}

uno::Reference< chart2::data::XDataSource > SwChartDataProvider::Impl_createDataSource(
        const uno::Sequence< beans::PropertyValue >& rArguments, bool bTestOnly )
{
    SolarMutexGuard aGuard;
    if (m_bDisposed)
        throw lang::DisposedException();

    if (!m_pDoc)
        throw uno::RuntimeException(u"Not connected to a document."_ustr);

    // get arguments
    OUString aRangeRepresentation;
    uno::Sequence< sal_Int32 > aSequenceMapping;
    bool bFirstIsLabel      = false;
    bool bDtaSrcIsColumns   = true// true : DataSource will be sequence of columns
                                    // false: DataSource will be sequence of rows

    OUString aChartOleObjectName; //work around wrong writer ranges ( see Issue 58464 )
    sal_Int32 nArgs = rArguments.getLength();
    OSL_ENSURE( nArgs != 0, "no properties provided" );
    if (nArgs == 0)
        return nullptr;
    for (const beans::PropertyValue& rArg : rArguments)
    {
        if ( rArg.Name == "DataRowSource" )
        {
            chart::ChartDataRowSource eSource;
            if (!(rArg.Value >>= eSource))
            {
                sal_Int32 nTmp = 0;
                if (!(rArg.Value >>= nTmp))
                    throw lang::IllegalArgumentException();
                eSource = static_cast< chart::ChartDataRowSource >( nTmp );
            }
            bDtaSrcIsColumns = eSource == chart::ChartDataRowSource_COLUMNS;
        }
        else if ( rArg.Name == "FirstCellAsLabel" )
        {
            if (!(rArg.Value >>= bFirstIsLabel))
                throw lang::IllegalArgumentException();
        }
        else if ( rArg.Name == "CellRangeRepresentation" )
        {
            if (!(rArg.Value >>= aRangeRepresentation))
                throw lang::IllegalArgumentException();
        }
        else if ( rArg.Name == "SequenceMapping" )
        {
            if (!(rArg.Value >>= aSequenceMapping))
                throw lang::IllegalArgumentException();
        }
        else if ( rArg.Name == "ChartOleObjectName" )
        {
            if (!(rArg.Value >>= aChartOleObjectName))
                throw lang::IllegalArgumentException();
        }
    }

    uno::Sequence< OUString > aSubRanges;
    // get sub-ranges and check that they all are from the very same table
    bool bOk = GetSubranges( aRangeRepresentation, aSubRanges, true );

    if (!bOk && m_pDoc && !aChartOleObjectName.isEmpty() )
    {
        //try to correct the range here
        //work around wrong writer ranges ( see Issue 58464 )
        UIName aChartTableName;

        const SwNodes& rNodes = m_pDoc->GetNodes();
        for( SwNodeOffset nN = rNodes.Count(); nN--; )
        {
            SwNode* pNode = rNodes[nN];
            if( !pNode )
                continue;
            const SwOLENode* pOleNode = pNode->GetOLENode();
            if( !pOleNode )
                continue;
            const SwOLEObj& rOObj = pOleNode->GetOLEObj();
            if( aChartOleObjectName == rOObj.GetCurrentPersistName() )
            {
                aChartTableName = pOleNode->GetChartTableName();
                break;
            }
        }

        if( !aChartTableName.isEmpty() )
        {
            //the wrong range is still shifted one row down
            //thus the first row is missing and an invalid row at the end is added.
            //Therefore we need to shift the range one row up
            SwRangeDescriptor aDesc;
            if (aRangeRepresentation.isEmpty())
                return nullptr;        // we can't handle this thus returning an empty references

            aRangeRepresentation = aRangeRepresentation.copy( 1 ); // get rid of '.' to have only the cell range left
            FillRangeDescriptor( aDesc, aRangeRepresentation );
            aDesc.Normalize();

            if (aDesc.nTop <= 0)    // no chance to shift the range one row up?
                return nullptr;        // we can't handle this thus returning an empty references

            aDesc.nTop      -= 1;
            aDesc.nBottom   -= 1;

            OUString aNewStartCell( sw_GetCellName( aDesc.nLeft, aDesc.nTop ) );
            OUString aNewEndCell( sw_GetCellName( aDesc.nRight, aDesc.nBottom ) );
            aRangeRepresentation = GetRangeRepFromTableAndCells(
                        aChartTableName, aNewStartCell, aNewEndCell, true );
            bOk = GetSubranges( aRangeRepresentation, aSubRanges, true );
        }
    }
    if (!bOk) // different tables used, or incorrect range specifiers
        throw lang::IllegalArgumentException();

    SortSubranges( aSubRanges, bDtaSrcIsColumns );

    // get table format for that single table from above
    SwFrameFormat    *pTableFormat  = nullptr;      // pointer to table format
    std::shared_ptr<SwUnoCursor> pUnoCursor;      // here required to check if the cells in the range do actually exist
    if (aSubRanges.hasElements())
        GetFormatAndCreateCursorFromRangeRep( m_pDoc, aSubRanges[0], &pTableFormat, pUnoCursor );

    if (!pTableFormat || !pUnoCursor)
        throw lang::IllegalArgumentException();

    SwTable* pTable = SwTable::FindTable(pTableFormat);
    if (pTable->IsTableComplex())
        return nullptr; // we can't handle this thus returning an empty references

    // get a character map in the size of the table to mark
    // all the ranges to use in
    sal_Int32 nRows = pTable->GetTabLines().size();
    sal_Int32 nCols = 0;
    // As per tdf#149718 one should know that some cells can be merged together.
    // Therefore, the number of columns (boxes in each row) are not necessarily
    // equal. Here, we calculate the maximum number of columns in all rows.
    for (sal_Int32 i = 0; i < nRows; ++i)
        nCols = std::max(nCols, static_cast<sal_Int32>(pTable->GetTabLines()[i]->GetTabBoxes().size()));

    std::vector<std::vector<char>> aMap(nRows);
    for (sal_Int32 i = 0; i < nRows; ++i)
        aMap[i].resize(nCols);

    // iterate over subranges and mark used cells in above map
    //!! by proceeding this way we automatically get rid of
    //!! multiple listed or overlapping cell ranges which should
    //!! just be ignored silently
    for (const OUString& rSubRange : aSubRanges)
    {
        UIName aTableName;
        OUString aStartCell, aEndCell;
        bool bOk2 = GetTableAndCellsFromRangeRep(
                            rSubRange, aTableName, aStartCell, aEndCell );
        OSL_ENSURE(bOk2, "failed to get table and start/end cells");

        sal_Int32 nStartRow, nStartCol, nEndRow, nEndCol;
        SwXTextTable::GetCellPosition(aStartCell, nStartCol, nStartRow);
        SwXTextTable::GetCellPosition(aEndCell, nEndCol, nEndRow);
        OSL_ENSURE( nStartRow <= nEndRow && nStartCol <= nEndCol,
                "cell range not normalized");

        // test if the ranges span more than the available cells
        if( nStartRow < 0 || nEndRow >= nRows ||
            nStartCol < 0 || nEndCol >= nCols )
        {
            throw lang::IllegalArgumentException();
        }
        for (sal_Int32 k1 = nStartRow;  k1 <= nEndRow;  ++k1)
        {
            for (sal_Int32 k2 = nStartCol;  k2 <= nEndCol;  ++k2)
                aMap[k1][k2] = 'x';
        }
    }

    // find label and data sequences to use

    sal_Int32 oi;  // outer index (slower changing index)
    sal_Int32 ii;  // inner index (faster changing index)
    sal_Int32 oiEnd = bDtaSrcIsColumns ? nCols : nRows;
    sal_Int32 iiEnd = bDtaSrcIsColumns ? nRows : nCols;
    std::vector<sal_Int32> aLabelIdx(oiEnd);
    std::vector<sal_Int32> aDataStartIdx(oiEnd);
    std::vector<sal_Int32> aDataLen(oiEnd);
    for (oi = 0; oi < oiEnd; ++oi)
    {
        aLabelIdx[oi]       = -1;
        aDataStartIdx[oi]   = -1;
        aDataLen[oi]        = 0;
    }

    for (oi = 0; oi < oiEnd; ++oi)
    {
        ii = 0;
        while (ii < iiEnd)
        {
            char &rChar = bDtaSrcIsColumns ? aMap[ii][oi] : aMap[oi][ii];

            // label should be used but is not yet found?
            if (rChar == 'x' && bFirstIsLabel && aLabelIdx[oi] == -1)
            {
                aLabelIdx[oi] = ii;
                rChar = 'L';    // setting a different char for labels here
                                // makes the test for the data sequence below
                                // easier
            }

            // find data sequence
            if (rChar == 'x' && aDataStartIdx[oi] == -1)
            {
                aDataStartIdx[oi] = ii;

                // get length of data sequence
                sal_Int32 nL = 0;
                while (ii< iiEnd && 'x' == (bDtaSrcIsColumns ? aMap[ii][oi] : aMap[oi][ii]))
                {
                    ++nL;   ++ii;
                }
                aDataLen[oi] = nL;

                // check that there is no other separate sequence of data
                // to be found because that is not supported
                while (ii < iiEnd)
                {
                    if ('x' == (bDtaSrcIsColumns ? aMap[ii][oi] : aMap[oi][ii]))
                        throw lang::IllegalArgumentException();
                    ++ii;
                }
            }
            else
                ++ii;
        }
    }

    // make some other consistency checks while calculating
    // the number of XLabeledDataSequence to build:
    // - labels should always be used or not at all
    // - the data sequences should have equal non-zero length
    sal_Int32 nNumLDS = 0;
    if (oiEnd > 0)
    {
        for (oi = 0; oi < oiEnd; ++oi)
        {
            // row/col used at all?
            if (aDataStartIdx[oi] != -1 &&
                (!bFirstIsLabel || aLabelIdx[oi] != -1))
            {
                ++nNumLDS;
            }
        }
    }
    if (nNumLDS == 0)
        throw lang::IllegalArgumentException();

    // now we should have all necessary data to build a proper DataSource
    // thus if we came this far there should be no further problem
    if (bTestOnly)
        return nullptr;    // have createDataSourcePossible return true

    // create data source from found label and data sequences
    uno::Sequence<uno::Reference<chart2::data::XDataSequence>> aLabelSeqs(nNumLDS);
    uno::Reference<chart2::data::XDataSequence>* pLabelSeqs = aLabelSeqs.getArray();
    uno::Sequence<uno::Reference<chart2::data::XDataSequence>> aDataSeqs(nNumLDS);
    uno::Reference<chart2::data::XDataSequence>* pDataSeqs = aDataSeqs.getArray();
    sal_Int32 nSeqsIdx = 0;
    for (oi = 0; oi < oiEnd; ++oi)
    {
        // row/col not used? (see if-statement above where nNumLDS was counted)
        if (!(aDataStartIdx[oi] != -1 &&
                (!bFirstIsLabel || aLabelIdx[oi] != -1)))
            continue;

        // get cell ranges for label and data

        SwRangeDescriptor aLabelDesc;
        SwRangeDescriptor aDataDesc;
        if (bDtaSrcIsColumns)   // use columns
        {
            aLabelDesc.nTop     = aLabelIdx[oi];
            aLabelDesc.nLeft    = oi;
            aLabelDesc.nBottom  = aLabelDesc.nTop;
            aLabelDesc.nRight   = oi;

            aDataDesc.nTop      = aDataStartIdx[oi];
            aDataDesc.nLeft     = oi;
            aDataDesc.nBottom   = aDataDesc.nTop + aDataLen[oi] - 1;
            aDataDesc.nRight    = oi;
        }
        else    // use rows
        {
            aLabelDesc.nTop     = oi;
            aLabelDesc.nLeft    = aLabelIdx[oi];
            aLabelDesc.nBottom  = oi;
            aLabelDesc.nRight   = aLabelDesc.nLeft;

            aDataDesc.nTop      = oi;
            aDataDesc.nLeft     = aDataStartIdx[oi];
            aDataDesc.nBottom   = oi;
            aDataDesc.nRight    = aDataDesc.nLeft + aDataLen[oi] - 1;
        }
        const OUString aBaseName = pTableFormat->GetName().toString() + ".";

        OUString aLabelRange;
        if (aLabelIdx[oi] != -1)
        {
            aLabelRange = aBaseName
                + sw_GetCellName( aLabelDesc.nLeft, aLabelDesc.nTop )
                + ":" + sw_GetCellName( aLabelDesc.nRight, aLabelDesc.nBottom );
        }

        OUString aDataRange = aBaseName
            + sw_GetCellName( aDataDesc.nLeft, aDataDesc.nTop )
            + ":" + sw_GetCellName( aDataDesc.nRight, aDataDesc.nBottom );

        // get cursors spanning the cell ranges for label and data
        std::shared_ptr<SwUnoCursor> pLabelUnoCursor;
        std::shared_ptr<SwUnoCursor> pDataUnoCursor;
        GetFormatAndCreateCursorFromRangeRep(m_pDoc, aLabelRange, &pTableFormat, pLabelUnoCursor);
        GetFormatAndCreateCursorFromRangeRep(m_pDoc, aDataRange, &pTableFormat, pDataUnoCursor);

        // create XDataSequence's from cursors
        if (pLabelUnoCursor)
            pLabelSeqs[nSeqsIdx] = new SwChartDataSequence(*this, *pTableFormat, pLabelUnoCursor);
        OSL_ENSURE(pDataUnoCursor, "pointer to data sequence missing");
        if (pDataUnoCursor)
            pDataSeqs[nSeqsIdx] = new SwChartDataSequence(*this, *pTableFormat, pDataUnoCursor);
        if (pLabelUnoCursor || pDataUnoCursor)
            ++nSeqsIdx;
    }
    OSL_ENSURE(nSeqsIdx == nNumLDS, "mismatch between sequence size and num,ber of entries");

    // build data source from data and label sequences
    uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence>> aLDS(nNumLDS);
    uno::Reference<chart2::data::XLabeledDataSequence>* pLDS = aLDS.getArray();
    for (sal_Int32 i = 0; i < nNumLDS; ++i)
    {
        rtl::Reference<SwChartLabeledDataSequence> pLabeledDtaSeq = new SwChartLabeledDataSequence;
        pLabeledDtaSeq->setLabel(pLabelSeqs[i]);
        pLabeledDtaSeq->setValues(pDataSeqs[i]);
        pLDS[i] = pLabeledDtaSeq;
    }

    // apply 'SequenceMapping' if it was provided
    if (aSequenceMapping.hasElements())
    {
        uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence>> aOld_LDS(aLDS);
        uno::Reference<chart2::data::XLabeledDataSequence>* pOld_LDS = aOld_LDS.getArray();

        sal_Int32 nNewCnt = 0;
        for (sal_Int32 nIdx : aSequenceMapping)
        {
            // check that index to be used is valid
            // and has not yet been used
            if (0 <= nIdx && nIdx < nNumLDS && pOld_LDS[nIdx].is())
            {
                pLDS[nNewCnt++] = pOld_LDS[nIdx];

                // mark index as being used already (avoids duplicate entries)
                pOld_LDS[nIdx].clear();
            }
        }
        // add not yet used 'old' sequences to new one
        for (sal_Int32 i = 0; i < nNumLDS; ++i)
        {
            if (pOld_LDS[i].is())
                pLDS[nNewCnt++] = pOld_LDS[i];
        }
        OSL_ENSURE(nNewCnt == nNumLDS, "unexpected size of resulting sequence");
    }

    return new SwChartDataSource(aLDS);
}

sal_Bool SAL_CALL SwChartDataProvider::createDataSourcePossible(
        const uno::Sequence< beans::PropertyValue >& rArguments )
{
    SolarMutexGuard aGuard;

    bool bPossible = true;
    try
    {
        Impl_createDataSource( rArguments, true );
    }
    catch (lang::IllegalArgumentException &)
    {
        bPossible = false;
    }

    return bPossible;
}

uno::Reference< chart2::data::XDataSource > SAL_CALL SwChartDataProvider::createDataSource(
        const uno::Sequence< beans::PropertyValue >& rArguments )
{
    SolarMutexGuard aGuard;
    return Impl_createDataSource( rArguments );
}

/**
 * Fix for #i79009
 * we need to return a property that has the same value as the property
 * 'CellRangeRepresentation' but for all rows which are increased by one.
 * E.g. Table1.A1:D5 -> Table1,A2:D6
 * Since the problem is only for old charts which did not support multiple
 * we do not need to provide that property/string if the 'CellRangeRepresentation'
 * contains multiple ranges.
 */

OUString SwChartDataProvider::GetBrokenCellRangeForExport(
    std::u16string_view rCellRangeRepresentation )
{
    // check that we do not have multiple ranges
    if (std::u16string_view::npos == rCellRangeRepresentation.find( ';' ))
    {
        // get current cell and table names
        UIName aTableName;
        OUString aStartCell, aEndCell;
        GetTableAndCellsFromRangeRep( rCellRangeRepresentation,
            aTableName, aStartCell, aEndCell, false );
        sal_Int32 nStartCol = -1, nStartRow = -1, nEndCol = -1, nEndRow = -1;
        SwXTextTable::GetCellPosition( aStartCell, nStartCol, nStartRow );
        SwXTextTable::GetCellPosition( aEndCell, nEndCol, nEndRow );

        // get new cell names
        ++nStartRow;
        ++nEndRow;
        aStartCell = sw_GetCellName( nStartCol, nStartRow );
        aEndCell   = sw_GetCellName( nEndCol, nEndRow );

        return GetRangeRepFromTableAndCells( aTableName,
                aStartCell, aEndCell, false );
    }

    return OUString();
}

uno::Sequence< beans::PropertyValue > SAL_CALL SwChartDataProvider::detectArguments(
        const uno::Reference< chart2::data::XDataSource >& xDataSource )
{
    SolarMutexGuard aGuard;
    if (m_bDisposed)
        throw lang::DisposedException();

    uno::Sequence< beans::PropertyValue > aResult;
    if (!xDataSource.is())
        return aResult;

    const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aDS_LDS( xDataSource->getDataSequences() );
    const uno::Reference< chart2::data::XLabeledDataSequence > *pDS_LDS = aDS_LDS.getConstArray();
    sal_Int32 nNumDS_LDS = aDS_LDS.getLength();

    if (nNumDS_LDS == 0)
    {
        OSL_FAIL( "XLabeledDataSequence in data source contains 0 entries" );
        return aResult;
    }

    SwFrameFormat *pTableFormat = nullptr;
    SwTable  *pTable    = nullptr;
    UIName  aTableName;
    sal_Int32 nTableRows = 0;
    sal_Int32 nTableCols = 0;

    // data used to build 'CellRangeRepresentation' from later on
    std::vector< std::vector< char > > aMap;

    uno::Sequence< sal_Int32 > aSequenceMapping( nNumDS_LDS );
    sal_Int32 *pSequenceMapping = aSequenceMapping.getArray();

    OUString aCellRanges;
    sal_Int16 nDtaSrcIsColumns = -1;// -1: don't know yet, 0: false, 1: true  -2: neither
    sal_Int32 nLabelSeqLen  = -1;   // used to see if labels are always used or not and have
                                    // the expected size of 1 (i.e. if FirstCellAsLabel can
                                    // be determined)
                                    // -1: don't know yet, 0: not used, 1: always a single labe cell, ...
                                    // -2: neither/failed
    for (sal_Int32 nDS1 = 0;  nDS1 < nNumDS_LDS;  ++nDS1)
    {
        uno::Reference< chart2::data::XLabeledDataSequence > xLabeledDataSequence( pDS_LDS[nDS1] );
        if( !xLabeledDataSequence.is() )
        {
            OSL_FAIL("got NULL for XLabeledDataSequence from Data source");
            continue;
        }
        const uno::Reference< chart2::data::XDataSequence > xCurLabel = xLabeledDataSequence->getLabel();
        const uno::Reference< chart2::data::XDataSequence > xCurValues = xLabeledDataSequence->getValues();

        // get sequence lengths for label and values.
        // (0 length is Ok)
        sal_Int32 nCurLabelSeqLen   = -1;
        sal_Int32 nCurValuesSeqLen  = -1;
        if (xCurLabel.is())
            nCurLabelSeqLen = xCurLabel->getData().getLength();
        if (xCurValues.is())
            nCurValuesSeqLen = xCurValues->getData().getLength();

        // check for consistent use of 'first cell as label'
        if (nLabelSeqLen == -1)     // set initial value to compare with below further on
            nLabelSeqLen = nCurLabelSeqLen;
        if (nLabelSeqLen != nCurLabelSeqLen)
            nLabelSeqLen = -2;  // failed / no consistent use of label cells

        // get table and cell names for label and values data sequences
        // (start and end cell will be sorted, i.e. start cell <= end cell)
        UIName aLabelTableName;
        OUString aLabelStartCell, aLabelEndCell;
        UIName aValuesTableName;
        OUString aValuesStartCell, aValuesEndCell;
        OUString aLabelRange, aValuesRange;
        if (xCurLabel.is())
            aLabelRange = xCurLabel->getSourceRangeRepresentation();
        if (xCurValues.is())
            aValuesRange = xCurValues->getSourceRangeRepresentation();
        if ((!aLabelRange.isEmpty() && !GetTableAndCellsFromRangeRep( aLabelRange,
                aLabelTableName, aLabelStartCell, aLabelEndCell ))  ||
            !GetTableAndCellsFromRangeRep( aValuesRange,
                aValuesTableName, aValuesStartCell, aValuesEndCell ))
        {
            return aResult; // failed -> return empty property sequence
        }

        // make sure all sequences use the same table
        if (aTableName.isEmpty())
            aTableName = aValuesTableName;  // get initial value to compare with
        if (aTableName.isEmpty() ||
             aTableName != aValuesTableName ||
            (!aLabelTableName.isEmpty() && aTableName != aLabelTableName))
        {
            return aResult; // failed -> return empty property sequence
        }

        // try to get 'DataRowSource' value (ROWS or COLUMNS) from inspecting
        // first and last cell used in both sequences

        sal_Int32 nFirstCol = -1, nFirstRow = -1, nLastCol = -1, nLastRow = -1;
        const OUString aCell( !aLabelStartCell.isEmpty() ? aLabelStartCell : aValuesStartCell );
        OSL_ENSURE( !aCell.isEmpty() , "start cell missing?" );
        SwXTextTable::GetCellPosition( aCell, nFirstCol, nFirstRow);
        SwXTextTable::GetCellPosition( aValuesEndCell, nLastCol, nLastRow);

        sal_Int16 nDirection = -1;  // -1: not yet set,  0: columns,  1: rows, -2: failed
        if (nFirstCol == nLastCol && nFirstRow == nLastRow) // a single cell...
        {
            OSL_ENSURE( nCurLabelSeqLen == 0 && nCurValuesSeqLen == 1,
                    "trying to determine 'DataRowSource': something's fishy... should have been a single cell");
            nDirection = 0;     // default direction for a single cell should be 'columns'
        }
        else    // more than one cell is available (in values and label together!)
        {
            if (nFirstCol == nLastCol && nFirstRow != nLastRow)
                nDirection = 1;
            else if (nFirstCol != nLastCol && nFirstRow == nLastRow)
                nDirection = 0;
            else
            {
                OSL_FAIL( "trying to determine 'DataRowSource': unexpected case found" );
                nDirection = -2;
            }
        }
        // check for consistent direction of data source
        if (nDtaSrcIsColumns == -1)     // set initial value to compare with below
            nDtaSrcIsColumns = nDirection;
        if (nDtaSrcIsColumns != nDirection)
        {
            nDtaSrcIsColumns = -2;  // failed
        }

        if (nDtaSrcIsColumns == 0 || nDtaSrcIsColumns == 1)
        {
            // build data to obtain 'SequenceMapping' later on

            OSL_ENSURE( nDtaSrcIsColumns == 0  ||   /* rows */
                        nDtaSrcIsColumns == 1,      /* columns */
                    "unexpected value for 'nDtaSrcIsColumns'" );
            pSequenceMapping[nDS1] = nDtaSrcIsColumns ? nFirstCol : nFirstRow;

            // build data used to determine 'CellRangeRepresentation' later on

            GetTableByName( *m_pDoc, aTableName, &pTableFormat, &pTable );
            if (!pTable || pTable->IsTableComplex())
                return aResult; // failed -> return empty property sequence
            nTableRows = pTable->GetTabLines().size();
            nTableCols = pTable->GetTabLines().front()->GetTabBoxes().size();
            aMap.resize( nTableRows );
            for (sal_Int32 i = 0;  i < nTableRows;  ++i)
                aMap[i].resize( nTableCols );

            if (!aLabelStartCell.isEmpty() && !aLabelEndCell.isEmpty())
            {
                sal_Int32 nStartCol = -1, nStartRow = -1, nEndCol = -1, nEndRow = -1;
                SwXTextTable::GetCellPosition( aLabelStartCell, nStartCol, nStartRow );
                SwXTextTable::GetCellPosition( aLabelEndCell,   nEndCol,   nEndRow );
                if (nStartRow < 0 || nEndRow >= nTableRows ||
                    nStartCol < 0 || nEndCol >= nTableCols)
                {
                    return aResult; // failed -> return empty property sequence
                }
                for (sal_Int32 i = nStartRow;  i <= nEndRow;  ++i)
                {
                    for (sal_Int32 k = nStartCol;  k <= nEndCol;  ++k)
                    {
                        char &rChar = aMap[i][k];
                        if (rChar == '\0')   // check for overlapping values and/or labels
                            rChar = 'L';
                        else
                            return aResult; // failed -> return empty property sequence
                    }
                }
            }
            if (!aValuesStartCell.isEmpty() && !aValuesEndCell.isEmpty())
            {
                sal_Int32 nStartCol = -1, nStartRow = -1, nEndCol = -1, nEndRow = -1;
                SwXTextTable::GetCellPosition( aValuesStartCell, nStartCol, nStartRow );
                SwXTextTable::GetCellPosition( aValuesEndCell,   nEndCol,   nEndRow );
                if (nStartRow < 0 || nEndRow >= nTableRows ||
                    nStartCol < 0 || nEndCol >= nTableCols)
                {
                    return aResult; // failed -> return empty property sequence
                }
                for (sal_Int32 i = nStartRow;  i <= nEndRow;  ++i)
                {
                    for (sal_Int32 k = nStartCol;  k <= nEndCol;  ++k)
                    {
                        char &rChar = aMap[i][k];
                        if (rChar == '\0')   // check for overlapping values and/or labels
                            rChar = 'x';
                        else
                            return aResult; // failed -> return empty property sequence
                    }
                }
            }
        }

#if OSL_DEBUG_LEVEL > 0
        // do some extra sanity checking that the length of the sequences
        // matches their range representation
        {
            sal_Int32 nStartRow = -1, nStartCol = -1, nEndRow = -1, nEndCol = -1;
            if (xCurLabel.is())
            {
                SwXTextTable::GetCellPosition( aLabelStartCell, nStartCol, nStartRow);
                SwXTextTable::GetCellPosition( aLabelEndCell,   nEndCol,   nEndRow);
                OSL_ENSURE( (nStartCol == nEndCol && (nEndRow - nStartRow + 1) == xCurLabel->getData().getLength()) ||
                            (nStartRow == nEndRow && (nEndCol - nStartCol + 1) == xCurLabel->getData().getLength()),
                        "label sequence length does not match range representation!" );
            }
            if (xCurValues.is())
            {
                SwXTextTable::GetCellPosition( aValuesStartCell, nStartCol, nStartRow);
                SwXTextTable::GetCellPosition( aValuesEndCell,   nEndCol,   nEndRow);
                OSL_ENSURE( (nStartCol == nEndCol && (nEndRow - nStartRow + 1) == xCurValues->getData().getLength()) ||
                            (nStartRow == nEndRow && (nEndCol - nStartCol + 1) == xCurValues->getData().getLength()),
                        "value sequence length does not match range representation!" );
            }
        }
#endif
    } // for

    // build value for 'CellRangeRepresentation'

    const OUString aCellRangeBase = aTableName.toString() + ".";
    OUString aCurRange;
    for (sal_Int32 i = 0;  i < nTableRows;  ++i)
    {
        for (sal_Int32 k = 0;  k < nTableCols;  ++k)
        {
            if (aMap[i][k] != '\0')  // top-left cell of a sub-range found
            {
                // find rectangular sub-range to use
                sal_Int32 nRowIndex1 = i;   // row index
                sal_Int32 nColIndex1 = k;   // column index
                sal_Int32 nRowSubLen = 0;
                sal_Int32 nColSubLen = 0;
                while (nRowIndex1 < nTableRows && aMap[nRowIndex1++][k] != '\0')
                    ++nRowSubLen;
                // be aware of shifted sequences!
                // (according to the checks done prior the length should be ok)
                while (nColIndex1 < nTableCols && aMap[i][nColIndex1] != '\0'
                                       && aMap[i + nRowSubLen-1][nColIndex1] != '\0')
                {
                    ++nColIndex1;
                    ++nColSubLen;
                }
                OUString aStartCell( sw_GetCellName( k, i ) );
                OUString aEndCell( sw_GetCellName( k + nColSubLen - 1, i + nRowSubLen - 1) );
                aCurRange = aCellRangeBase + aStartCell + ":" + aEndCell;
                if (!aCellRanges.isEmpty())
                    aCellRanges += ";";
                aCellRanges += aCurRange;

                // clear already found sub-range from map
                for (sal_Int32 nRowIndex2 = 0;  nRowIndex2 < nRowSubLen;  ++nRowIndex2)
                    for (sal_Int32 nColumnIndex2 = 0;  nColumnIndex2 < nColSubLen;  ++nColumnIndex2)
                        aMap[i + nRowIndex2][k + nColumnIndex2] = '\0';
            }
        }
    }
    // to be nice to the user we now sort the cell ranges according to
    // rows or columns depending on the direction used in the data source
    uno::Sequence< OUString > aSortedRanges;
    GetSubranges( aCellRanges, aSortedRanges, false /*sub ranges should already be normalized*/ );
    SortSubranges( aSortedRanges, (nDtaSrcIsColumns == 1) );
    OUString aSortedCellRanges;
    for (const OUString& rSortedRange : aSortedRanges)
    {
        if (!aSortedCellRanges.isEmpty())
            aSortedCellRanges += ";";
        aSortedCellRanges += rSortedRange;
    }

    // build value for 'SequenceMapping'

    uno::Sequence< sal_Int32 > aSortedMapping( aSequenceMapping );
    auto [begin, end] = asNonConstRange(aSortedMapping);
    std::sort(begin, end);
    bool bNeedSequenceMapping = false;
    for (sal_Int32 i = 0;  i < aSequenceMapping.getLength();  ++i)
    {
        auto it = std::find( std::cbegin(aSortedMapping), std::cend(aSortedMapping),
                             aSequenceMapping[i] );
        pSequenceMapping[i] = std::distance(std::cbegin(aSortedMapping), it);

        if (i != aSequenceMapping[i])
            bNeedSequenceMapping = true;
    }

    // check if 'SequenceMapping' is actually not required...
    // (don't write unnecessary properties to the XML file)
    if (!bNeedSequenceMapping)
        aSequenceMapping.realloc(0);

    // build resulting properties

    OSL_ENSURE(nLabelSeqLen >= 0 || nLabelSeqLen == -2 /*not used*/,
            "unexpected value for 'nLabelSeqLen'" );
    bool bFirstCellIsLabel = false;     // default value if 'nLabelSeqLen' could not properly determined
    if (nLabelSeqLen > 0) // == 0 means no label sequence in use
        bFirstCellIsLabel = true;

    OSL_ENSURE( !aSortedCellRanges.isEmpty(), "CellRangeRepresentation missing" );
    const OUString aBrokenCellRangeForExport( GetBrokenCellRangeForExport( aSortedCellRanges ) );

    aResult.realloc(5);
    auto pResult = aResult.getArray();
    sal_Int32 nProps = 0;
    pResult[nProps  ].Name = "FirstCellAsLabel";
    pResult[nProps++].Value <<= bFirstCellIsLabel;
    pResult[nProps  ].Name = "CellRangeRepresentation";
    pResult[nProps++].Value <<= aSortedCellRanges;
    if (!aBrokenCellRangeForExport.isEmpty())
    {
        pResult[nProps  ].Name = "BrokenCellRangeForExport";
        pResult[nProps++].Value <<= aBrokenCellRangeForExport;
    }
    if (nDtaSrcIsColumns == 0 || nDtaSrcIsColumns == 1)
    {
        chart::ChartDataRowSource eDataRowSource = (nDtaSrcIsColumns == 1) ?
                    chart::ChartDataRowSource_COLUMNS : chart::ChartDataRowSource_ROWS;
        pResult[nProps  ].Name = "DataRowSource";
        pResult[nProps++].Value <<= eDataRowSource;

        if (aSequenceMapping.hasElements())
        {
            pResult[nProps  ].Name = "SequenceMapping";
            pResult[nProps++].Value <<= aSequenceMapping;
        }
    }
    aResult.realloc( nProps );

    return aResult;
}

uno::Reference< chart2::data::XDataSequence > SwChartDataProvider::Impl_createDataSequenceByRangeRepresentation(
        std::u16string_view rRangeRepresentation, bool bTestOnly )
{
    if (m_bDisposed)
        throw lang::DisposedException();

    SwFrameFormat    *pTableFormat    = nullptr;    // pointer to table format
    std::shared_ptr<SwUnoCursor> pUnoCursor;    // pointer to new created cursor spanning the cell range
    GetFormatAndCreateCursorFromRangeRep( m_pDoc, rRangeRepresentation,
                                          &pTableFormat, pUnoCursor );
    if (!pTableFormat || !pUnoCursor)
        throw lang::IllegalArgumentException();

    // check that cursors point and mark are in a single row or column.
    OUString aCellRange( GetCellRangeName( *pTableFormat, *pUnoCursor ) );
    SwRangeDescriptor aDesc;
    FillRangeDescriptor( aDesc, aCellRange );
    if (aDesc.nTop != aDesc.nBottom  &&  aDesc.nLeft != aDesc.nRight)
        throw lang::IllegalArgumentException();

    if (bTestOnly)
        return nullptr;
    return new SwChartDataSequence( *this, *pTableFormat, pUnoCursor );
}

sal_Bool SAL_CALL SwChartDataProvider::createDataSequenceByRangeRepresentationPossible(
        const OUString& rRangeRepresentation )
{
    SolarMutexGuard aGuard;

    bool bPossible = true;
    try
    {
        Impl_createDataSequenceByRangeRepresentation( rRangeRepresentation, true );
    }
    catch (lang::IllegalArgumentException &)
    {
        bPossible = false;
    }

    return bPossible;
}

uno::Reference< chart2::data::XDataSequence > SAL_CALL SwChartDataProvider::createDataSequenceByRangeRepresentation(
        const OUString& rRangeRepresentation )
{
    SolarMutexGuard aGuard;
    return Impl_createDataSequenceByRangeRepresentation( rRangeRepresentation );
}

uno::Reference< sheet::XRangeSelection > SAL_CALL SwChartDataProvider::getRangeSelection(  )
{
    // note: it is no error to return nothing here
    return uno::Reference< sheet::XRangeSelection >();
}

uno::Reference<css::chart2::data::XDataSequence> SAL_CALL
    SwChartDataProvider::createDataSequenceByValueArray(
        const OUString& /*aRole*/, const OUString& /*aRangeRepresentation*/,
        const OUString& /*aRoleQualifier*/ )
{
    return uno::Reference<css::chart2::data::XDataSequence>();
}

void SAL_CALL SwChartDataProvider::dispose(  )
{
    bool bMustDispose( false );
    {
        std::unique_lock aGuard( GetChartMutex() );
        bMustDispose = !m_bDisposed;
        if (!m_bDisposed)
            m_bDisposed = true;
    }
    if (!bMustDispose)
        return;

    // dispose all data-sequences
    for (const auto& rEntry : m_aDataSequences)
    {
        DisposeAllDataSequences( rEntry.first );
    }
    // release all references to data-sequences
    m_aDataSequences.clear();

    // require listeners to release references to this object
    lang::EventObject aEvtObj( static_cast< chart2::data::XDataProvider * >(this) );
    std::unique_lock aGuard( GetChartMutex() );
    m_aEventListeners.disposeAndClear( aGuard, aEvtObj );
}

void SAL_CALL SwChartDataProvider::addEventListener(
        const uno::Reference< lang::XEventListener >& rxListener )
{
    std::unique_lock aGuard( GetChartMutex() );
    if (!m_bDisposed && rxListener.is())
        m_aEventListeners.addInterface( aGuard, rxListener );
}

void SAL_CALL SwChartDataProvider::removeEventListener(
        const uno::Reference< lang::XEventListener >& rxListener )
{
    std::unique_lock aGuard( GetChartMutex() );
    if (!m_bDisposed && rxListener.is())
        m_aEventListeners.removeInterface( aGuard, rxListener );
}

OUString SAL_CALL SwChartDataProvider::getImplementationName(  )
{
    return u"SwChartDataProvider"_ustr;
}

sal_Bool SAL_CALL SwChartDataProvider::supportsService(const OUString& rServiceName )
{
    return cppu::supportsService(this, rServiceName);
}

uno::Sequence< OUString > SAL_CALL SwChartDataProvider::getSupportedServiceNames(  )
{
    return { u"com.sun.star.chart2.data.DataProvider"_ustr};
}

void SwChartDataProvider::AddDataSequence( const SwTable &rTable, rtl::Reference< SwChartDataSequence > const &rxDataSequence )
{
    Vec_DataSequenceRef_t& rVec = m_aDataSequences[ &rTable ];
    assert(std::find_if(rVec.begin(), rVec.end(),
        [&rxDataSequence](const unotools::WeakReference < SwChartDataSequence >& i)
        {
            return i.get() == rxDataSequence;
        }) == rVec.end() && "duplicate insert");
    rVec.push_back( rxDataSequence );
}

void SwChartDataProvider::RemoveDataSequence( const SwTable &rTable, rtl::Reference< SwChartDataSequence > const &rxDataSequence )
{
    Vec_DataSequenceRef_t& rVec = m_aDataSequences[ &rTable ];
    std::erase_if(rVec,
        [&rxDataSequence](const unotools::WeakReference < SwChartDataSequence >& i)
        {
            return i.get() == rxDataSequence;
        });
}

void SwChartDataProvider::InvalidateTable( const SwTable *pTable, bool bImmediate )
{
    OSL_ENSURE( pTable, "table pointer is NULL" );
    if (!pTable)
        return;

    if (!m_bDisposed)
       pTable->GetFrameFormat()->GetDoc().getIDocumentChartDataProviderAccess().GetChartControllerHelper().StartOrContinueLocking();

    const Vec_DataSequenceRef_t &rVec = m_aDataSequences[ pTable ];
    for (const unotools::WeakReference<SwChartDataSequence>& rItem : rVec)
    {
        rtl::Reference< SwChartDataSequence > xRef(rItem);
        if (xRef.is())
        {
            // mark the sequence as 'dirty' and notify listeners
            xRef->setModified( true );
        }
    }

    // tdf#122995 added Immediate-mode to allow non-timer-delayed Chart invalidation
    if (bImmediate && !m_bDisposed)
       pTable->GetFrameFormat()->GetDoc().getIDocumentChartDataProviderAccess().GetChartControllerHelper().Disconnect();
}

void SwChartDataProvider::DeleteBox( const SwTable *pTable, const SwTableBox &rBox )
{
    OSL_ENSURE( pTable, "table pointer is NULL" );
    if (!pTable)
        return;

    if (!m_bDisposed)
        pTable->GetFrameFormat()->GetDoc().getIDocumentChartDataProviderAccess().GetChartControllerHelper().StartOrContinueLocking();

    Vec_DataSequenceRef_t &rVec = m_aDataSequences[ pTable ];

    // iterate over all data-sequences for that table...
    auto aIt( rVec.begin() );
    while (aIt != rVec.end())
    {
        bool bNowEmpty = false;
        bool bSeqDisposed = false;

        // check if weak reference is still valid...
        rtl::Reference< SwChartDataSequence > pDataSeq(*aIt);
        if (pDataSeq.is())
        {
            // then delete that table box (check if implementation cursor needs to be adjusted)
            try
            {
                bNowEmpty = pDataSeq->DeleteBox( rBox );
            }
            catch (const lang::DisposedException&)
            {
                bNowEmpty = true;
                bSeqDisposed = true;
            }
        }

        if (bNowEmpty)
        {
            aIt = rVec.erase( aIt );
            if (pDataSeq && !bSeqDisposed)
                pDataSeq->dispose();    // the current way to tell chart that sth. got removed
        }
        else
            ++aIt;
    }
}

void SwChartDataProvider::DisposeAllDataSequences( const SwTable *pTable )
{
    OSL_ENSURE( pTable, "table pointer is NULL" );
    if (!pTable)
        return;

    if (!m_bDisposed)
        pTable->GetFrameFormat()->GetDoc().getIDocumentChartDataProviderAccess().GetChartControllerHelper().StartOrContinueLocking();

    //! make a copy of the STL container!
    //! This is necessary since calling 'dispose' will implicitly remove an element
    //! of the original container, and thus any iterator in the original container
    //! would become invalid.
    const Vec_DataSequenceRef_t aVec( m_aDataSequences[ pTable ] );

    for (const unotools::WeakReference<SwChartDataSequence>& rItem : aVec)
    {
        rtl::Reference< SwChartDataSequence > xRef(rItem);
        if (xRef.is())
        {
            xRef->dispose();
        }
    }
}

/**
 * SwChartDataProvider::AddRowCols tries to notify charts of added columns
 * or rows and extends the value sequence respectively (if possible).
 * If those can be added to the end of existing value data-sequences those
 * sequences get modified accordingly and will send a modification
 * notification (calling 'setModified
 *
 * Since this function is a work-around for non existent Writer core functionality
 * (no arbitrary multi-selection in tables that can be used to define a
 * data-sequence) this function will be somewhat unreliable.
 * For example we will only try to adapt value sequences. For this we assume
 * that a sequence of length 1 is a label sequence and those with length >= 2
 * we presume to be value sequences. Also new cells can only be added in the
 * direction the value sequence is already pointing (rows / cols) and at the
 * start or end of the values data-sequence.
 * Nothing needs to be done if the new cells are in between the table cursors
 * point and mark since data-sequence are considered to consist of all cells
 * between those.
 * New rows/cols need to be added already to the table before calling
 * this function.
 */

void SwChartDataProvider::AddRowCols(
        const SwTable &rTable,
        const SwSelBoxes& rBoxes,
        sal_uInt16 nLines, bool bBehind )
{
    if (rTable.IsTableComplex())
        return;

    const size_t nBoxes = rBoxes.size();
    if (nBoxes < 1 || nLines < 1)
        return;

    SwTableBox* pFirstBox   = rBoxes[0];
    SwTableBox* pLastBox    = rBoxes.back();

    if (!(pFirstBox && pLastBox))
        return;

    sal_Int32 nFirstCol = -1, nFirstRow = -1, nLastCol = -1, nLastRow = -1;
    SwXTextTable::GetCellPosition( pFirstBox->GetName(), nFirstCol, nFirstRow  );
    SwXTextTable::GetCellPosition( pLastBox->GetName(),  nLastCol,  nLastRow );

    bool bAddCols = false;  // default; also to be used if nBoxes == 1 :-/
    if (nFirstCol == nLastCol && nFirstRow != nLastRow)
        bAddCols = true;
    if (nFirstCol != nLastCol && nFirstRow != nLastRow)
        return;

    //get range of indices in col/rows for new cells
    sal_Int32 nFirstNewCol = nFirstCol;
    sal_Int32 nFirstNewRow = bBehind ?  nFirstRow + 1 : nFirstRow - nLines;
    if (bAddCols)
    {
        OSL_ENSURE( nFirstCol == nLastCol, "column indices seem broken" );
        nFirstNewCol = bBehind ?  nFirstCol + 1 : nFirstCol - nLines;
        nFirstNewRow = nFirstRow;
    }

    // iterate over all data-sequences for the table
    const Vec_DataSequenceRef_t &rVec = m_aDataSequences[ &rTable ];
    for (const unotools::WeakReference<SwChartDataSequence>& rItem : rVec)
    {
        rtl::Reference< SwChartDataSequence > pDataSeq(rItem);
        if (pDataSeq.is())
        {
            const sal_Int32 nLen = pDataSeq->getTextualData().getLength();
            if (nLen > 1) // value data-sequence ?
            {
                SwRangeDescriptor aDesc;
                pDataSeq->FillRangeDesc( aDesc );

                chart::ChartDataRowSource eDRSource = chart::ChartDataRowSource_COLUMNS;
                if (aDesc.nTop == aDesc.nBottom && aDesc.nLeft != aDesc.nRight)
                    eDRSource = chart::ChartDataRowSource_ROWS;

                if (!bAddCols && eDRSource == chart::ChartDataRowSource_COLUMNS)
                {
                    // add rows: extend affected columns by newly added row cells
                    pDataSeq->ExtendTo( true, nFirstNewRow, nLines );
                }
                else if (bAddCols && eDRSource == chart::ChartDataRowSource_ROWS)
                {
                    // add cols: extend affected rows by newly added column cells
                    pDataSeq->ExtendTo( false, nFirstNewCol, nLines );
                }
            }
        }
    }
}

// XRangeXMLConversion
OUString SAL_CALL SwChartDataProvider::convertRangeToXML( const OUString& rRangeRepresentation )
{
    SolarMutexGuard aGuard;
    if (m_bDisposed)
        throw lang::DisposedException();

    if (rRangeRepresentation.isEmpty())
        return OUString();

    OUStringBuffer aRes;

    // multiple ranges are delimited by a ';' like in
    // "Table1.A1:A4;Table1.C2:C5" the same table must be used in all ranges!
    SwTable* pFirstFoundTable = nullptr;  // to check that only one table will be used
    sal_Int32 nPos = 0;
    do {
        const OUString aRange( rRangeRepresentation.getToken(0, ';', nPos) );
        SwFrameFormat    *pTableFormat  = nullptr; // pointer to table format
        std::shared_ptr<SwUnoCursor> pCursor;
        GetFormatAndCreateCursorFromRangeRep( m_pDoc, aRange, &pTableFormat, pCursor );
        if (!pTableFormat)
            throw lang::IllegalArgumentException();
        SwTable* pTable = SwTable::FindTable( pTableFormat );
        if  (pTable->IsTableComplex())
            throw uno::RuntimeException(u"Table too complex."_ustr);

        // check that there is only one table used in all ranges
        if (!pFirstFoundTable)
            pFirstFoundTable = pTable;
        if (pTable != pFirstFoundTable)
            throw lang::IllegalArgumentException();

        UIName aTableName;
        OUString aStartCell;
        OUString aEndCell;
        if (!GetTableAndCellsFromRangeRep( aRange, aTableName, aStartCell, aEndCell ))
            throw lang::IllegalArgumentException();

        sal_Int32 nCol, nRow;
        SwXTextTable::GetCellPosition( aStartCell, nCol, nRow );
        if (nCol < 0 || nRow < 0)
            throw uno::RuntimeException(u"Cell not found."_ustr);

        //!! following objects/functions are implemented in XMLRangeHelper.?xx
        //!! which is a copy of the respective file from chart2 !!
        XMLRangeHelper::CellRange aCellRange;
        aCellRange.aTableName = aTableName.toString();
        aCellRange.aUpperLeft.nColumn   = nCol;
        aCellRange.aUpperLeft.nRow      = nRow;
        aCellRange.aUpperLeft.bIsEmpty  = false;
        if (aStartCell != aEndCell && !aEndCell.isEmpty())
        {
            SwXTextTable::GetCellPosition( aEndCell, nCol, nRow );
            if (nCol < 0 || nRow < 0)
                throw uno::RuntimeException(u"Cell not found."_ustr);

            aCellRange.aLowerRight.nColumn   = nCol;
            aCellRange.aLowerRight.nRow      = nRow;
            aCellRange.aLowerRight.bIsEmpty  = false;
        }
        OUString aTmp( XMLRangeHelper::getXMLStringFromCellRange( aCellRange ) );
        if (!aRes.isEmpty()) // in case of multiple ranges add delimiter
            aRes.append(" ");
        aRes.append(aTmp);
    }
    while (nPos>0);

    return aRes.makeStringAndClear();
}

OUString SAL_CALL SwChartDataProvider::convertRangeFromXML( const OUString& rXMLRange )
{
    SolarMutexGuard aGuard;
    if (m_bDisposed)
        throw lang::DisposedException();

    if (rXMLRange.isEmpty())
        return OUString();

    OUStringBuffer aRes;

    // multiple ranges are delimited by a ' ' like in
    // "Table1.$A$1:.$A$4 Table1.$C$2:.$C$5" the same table must be used in all ranges!
    OUString aFirstFoundTable; // to check that only one table will be used
    sal_Int32 nPos = 0;
    do
    {
        OUString aRange( rXMLRange.getToken(0, ' ', nPos) );

        //!! following objects and function are implemented in XMLRangeHelper.?xx
        //!! which is a copy of the respective file from chart2 !!
        XMLRangeHelper::CellRange aCellRange( XMLRangeHelper::getCellRangeFromXMLString( aRange ));

        // check that there is only one table used in all ranges
        if (aFirstFoundTable.isEmpty())
            aFirstFoundTable = aCellRange.aTableName;
        if (aCellRange.aTableName != aFirstFoundTable)
            throw lang::IllegalArgumentException();

        OUString aTmp = aCellRange.aTableName + "." +
--> --------------------

--> maximum size reached

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

Messung V0.5
C=91 H=87 G=88

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© 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.