diff options
Diffstat (limited to 'sc/source/core/data/dpobject.cxx')
-rw-r--r-- | sc/source/core/data/dpobject.cxx | 3952 |
1 files changed, 3952 insertions, 0 deletions
diff --git a/sc/source/core/data/dpobject.cxx b/sc/source/core/data/dpobject.cxx new file mode 100644 index 000000000..fc31af3c6 --- /dev/null +++ b/sc/source/core/data/dpobject.cxx @@ -0,0 +1,3952 @@ +/* -*- 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 <docsh.hxx> +#include <dpcache.hxx> +#include <dpobject.hxx> +#include <dptabsrc.hxx> +#include <dpsave.hxx> +#include <dpdimsave.hxx> +#include <dpoutput.hxx> +#include <dpshttab.hxx> +#include <dpsdbtab.hxx> +#include <dpgroup.hxx> +#include <document.hxx> +#include <pivot.hxx> +#include <dapiuno.hxx> +#include <miscuno.hxx> +#include <refupdat.hxx> +#include <attrib.hxx> +#include <scitems.hxx> +#include <unonames.hxx> +#include <dpglobal.hxx> +#include <globstr.hrc> +#include <queryentry.hxx> +#include <dputil.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/sdb/XCompletedExecution.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbc/XResultSetMetaData.hpp> +#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/sheet/GeneralFunction2.hpp> +#include <com/sun/star/sheet/DataPilotFieldFilter.hpp> +#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp> +#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp> +#include <com/sun/star/sheet/DataPilotTableHeaderData.hpp> +#include <com/sun/star/sheet/DataPilotTablePositionData.hpp> +#include <com/sun/star/sheet/DataPilotTablePositionType.hpp> +#include <com/sun/star/sheet/DimensionFlags.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/sheet/XDrillDownDataSupplier.hpp> + +#include <unotools/charclass.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <comphelper/types.hxx> +#include <o3tl/safeint.hxx> +#include <sal/macros.h> +#include <svl/numformat.hxx> +#include <tools/diagnose_ex.h> +#include <svl/zforlist.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <utility> +#include <vector> +#include <memory> +#include <algorithm> + +using namespace com::sun::star; +using ::std::vector; +using ::std::shared_ptr; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::lang::XComponent; +using ::com::sun::star::sheet::DataPilotTableHeaderData; +using ::com::sun::star::sheet::DataPilotTablePositionData; +using ::com::sun::star::sheet::XDimensionsSupplier; +using ::com::sun::star::beans::XPropertySet; + +constexpr OUStringLiteral SC_SERVICE_ROWSET = u"com.sun.star.sdb.RowSet"; + +constexpr OUStringLiteral SC_DBPROP_DATASOURCENAME = u"DataSourceName"; +constexpr OUStringLiteral SC_DBPROP_COMMAND = u"Command"; +constexpr OUStringLiteral SC_DBPROP_COMMANDTYPE = u"CommandType"; + +constexpr OUStringLiteral SCDPSOURCE_SERVICE = u"com.sun.star.sheet.DataPilotSource"; + +namespace { + +/** + * Database connection implementation for UNO database API. Note that in + * the UNO database API, column index is 1-based, whereas the interface + * requires that column index be 0-based. + */ +class DBConnector : public ScDPCache::DBConnector +{ + ScDPCache& mrCache; + + uno::Reference<sdbc::XRowSet> mxRowSet; + uno::Reference<sdbc::XRow> mxRow; + uno::Reference<sdbc::XResultSetMetaData> mxMetaData; + Date maNullDate; + +public: + DBConnector(ScDPCache& rCache, const uno::Reference<sdbc::XRowSet>& xRowSet, const Date& rNullDate); + + bool isValid() const; + + virtual void getValue(tools::Long nCol, ScDPItemData &rData, SvNumFormatType& rNumType) const override; + virtual OUString getColumnLabel(tools::Long nCol) const override; + virtual tools::Long getColumnCount() const override; + virtual bool first() override; + virtual bool next() override; + virtual void finish() override; +}; + +DBConnector::DBConnector(ScDPCache& rCache, const uno::Reference<sdbc::XRowSet>& xRowSet, const Date& rNullDate) : + mrCache(rCache), mxRowSet(xRowSet), maNullDate(rNullDate) +{ + Reference<sdbc::XResultSetMetaDataSupplier> xMetaSupp(mxRowSet, UNO_QUERY); + if (xMetaSupp.is()) + mxMetaData = xMetaSupp->getMetaData(); + + mxRow.set(mxRowSet, UNO_QUERY); +} + +bool DBConnector::isValid() const +{ + return mxRowSet.is() && mxRow.is() && mxMetaData.is(); +} + +bool DBConnector::first() +{ + return mxRowSet->first(); +} + +bool DBConnector::next() +{ + return mxRowSet->next(); +} + +void DBConnector::finish() +{ + mxRowSet->beforeFirst(); +} + +tools::Long DBConnector::getColumnCount() const +{ + return mxMetaData->getColumnCount(); +} + +OUString DBConnector::getColumnLabel(tools::Long nCol) const +{ + return mxMetaData->getColumnLabel(nCol+1); +} + +void DBConnector::getValue(tools::Long nCol, ScDPItemData &rData, SvNumFormatType& rNumType) const +{ + rNumType = SvNumFormatType::NUMBER; + sal_Int32 nType = mxMetaData->getColumnType(nCol+1); + + try + { + double fValue = 0.0; + switch (nType) + { + case sdbc::DataType::BIT: + case sdbc::DataType::BOOLEAN: + { + rNumType = SvNumFormatType::LOGICAL; + fValue = mxRow->getBoolean(nCol+1) ? 1 : 0; + rData.SetValue(fValue); + break; + } + case sdbc::DataType::TINYINT: + case sdbc::DataType::SMALLINT: + case sdbc::DataType::INTEGER: + case sdbc::DataType::BIGINT: + case sdbc::DataType::FLOAT: + case sdbc::DataType::REAL: + case sdbc::DataType::DOUBLE: + case sdbc::DataType::NUMERIC: + case sdbc::DataType::DECIMAL: + { + //TODO: do the conversion here? + fValue = mxRow->getDouble(nCol+1); + rData.SetValue(fValue); + break; + } + case sdbc::DataType::DATE: + { + rNumType = SvNumFormatType::DATE; + + util::Date aDate = mxRow->getDate(nCol+1); + fValue = Date(aDate.Day, aDate.Month, aDate.Year) - maNullDate; + rData.SetValue(fValue); + break; + } + case sdbc::DataType::TIME: + { + rNumType = SvNumFormatType::TIME; + + util::Time aTime = mxRow->getTime(nCol+1); + fValue = aTime.Hours / static_cast<double>(::tools::Time::hourPerDay) + + aTime.Minutes / static_cast<double>(::tools::Time::minutePerDay) + + aTime.Seconds / static_cast<double>(::tools::Time::secondPerDay) + + aTime.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay); + rData.SetValue(fValue); + break; + } + case sdbc::DataType::TIMESTAMP: + { + rNumType = SvNumFormatType::DATETIME; + + util::DateTime aStamp = mxRow->getTimestamp(nCol+1); + fValue = ( Date( aStamp.Day, aStamp.Month, aStamp.Year ) - maNullDate ) + + aStamp.Hours / static_cast<double>(::tools::Time::hourPerDay) + + aStamp.Minutes / static_cast<double>(::tools::Time::minutePerDay) + + aStamp.Seconds / static_cast<double>(::tools::Time::secondPerDay) + + aStamp.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay); + rData.SetValue(fValue); + break; + } + case sdbc::DataType::CHAR: + case sdbc::DataType::VARCHAR: + case sdbc::DataType::LONGVARCHAR: + case sdbc::DataType::SQLNULL: + case sdbc::DataType::BINARY: + case sdbc::DataType::VARBINARY: + case sdbc::DataType::LONGVARBINARY: + default: + // nCol is 0-based, and the left-most column always has nCol == 0. + rData.SetStringInterned( + mrCache.InternString(nCol, mxRow->getString(nCol+1))); + } + } + catch (uno::Exception&) + { + rData.SetEmpty(); + } +} + +} + +static sheet::DataPilotFieldOrientation lcl_GetDataGetOrientation( const uno::Reference<sheet::XDimensionsSupplier>& xSource ) +{ + sheet::DataPilotFieldOrientation nRet = sheet::DataPilotFieldOrientation_HIDDEN; + if ( xSource.is() ) + { + uno::Reference<container::XNameAccess> xDimNameAccess = xSource->getDimensions(); + const uno::Sequence<OUString> aDimNames = xDimNameAccess->getElementNames(); + for (const OUString& rDimName : aDimNames) + { + uno::Reference<beans::XPropertySet> xDimProp(xDimNameAccess->getByName(rDimName), + uno::UNO_QUERY); + if ( xDimProp.is() ) + { + const bool bFound = ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ); + //TODO: error checking -- is "IsDataLayoutDimension" property required?? + if (bFound) + { + nRet = ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + break; + } + } + } + } + return nRet; +} + +ScDPServiceDesc::ScDPServiceDesc( + const OUString& rServ, const OUString& rSrc, const OUString& rNam, + const OUString& rUser, const OUString& rPass ) : + aServiceName( rServ ), + aParSource( rSrc ), + aParName( rNam ), + aParUser( rUser ), + aParPass( rPass ) {} + +bool ScDPServiceDesc::operator== ( const ScDPServiceDesc& rOther ) const +{ + return aServiceName == rOther.aServiceName && + aParSource == rOther.aParSource && + aParName == rOther.aParName && + aParUser == rOther.aParUser && + aParPass == rOther.aParPass; +} + +ScDPObject::ScDPObject( ScDocument* pD ) : + pDoc( pD ), + nHeaderRows( 0 ), + mbHeaderLayout(false), + bAllowMove(false), + bSettingsChanged(false), + mbEnableGetPivotData(true) +{ +} + +ScDPObject::ScDPObject(const ScDPObject& r) : + pDoc( r.pDoc ), + aTableName( r.aTableName ), + aTableTag( r.aTableTag ), + aOutRange( r.aOutRange ), + maInteropGrabBag(r.maInteropGrabBag), + nHeaderRows( r.nHeaderRows ), + mbHeaderLayout( r.mbHeaderLayout ), + bAllowMove(false), + bSettingsChanged(false), + mbEnableGetPivotData(r.mbEnableGetPivotData) +{ + if (r.pSaveData) + pSaveData.reset( new ScDPSaveData(*r.pSaveData) ); + if (r.pSheetDesc) + pSheetDesc.reset( new ScSheetSourceDesc(*r.pSheetDesc) ); + if (r.pImpDesc) + pImpDesc.reset( new ScImportSourceDesc(*r.pImpDesc) ); + if (r.pServDesc) + pServDesc.reset( new ScDPServiceDesc(*r.pServDesc) ); + // xSource (and pOutput) is not copied +} + +ScDPObject::~ScDPObject() +{ + Clear(); +} + +ScDPObject& ScDPObject::operator= (const ScDPObject& r) +{ + if (this != &r) + { + Clear(); + + pDoc = r.pDoc; + aTableName = r.aTableName; + aTableTag = r.aTableTag; + aOutRange = r.aOutRange; + maInteropGrabBag = r.maInteropGrabBag; + nHeaderRows = r.nHeaderRows; + mbHeaderLayout = r.mbHeaderLayout; + bAllowMove = false; + bSettingsChanged = false; + mbEnableGetPivotData = r.mbEnableGetPivotData; + + if (r.pSaveData) + pSaveData.reset( new ScDPSaveData(*r.pSaveData) ); + if (r.pSheetDesc) + pSheetDesc.reset( new ScSheetSourceDesc(*r.pSheetDesc) ); + if (r.pImpDesc) + pImpDesc.reset( new ScImportSourceDesc(*r.pImpDesc) ); + if (r.pServDesc) + pServDesc.reset( new ScDPServiceDesc(*r.pServDesc) ); + } + return *this; +} + +void ScDPObject::EnableGetPivotData(bool b) +{ + mbEnableGetPivotData = b; +} + +void ScDPObject::SetAllowMove(bool bSet) +{ + bAllowMove = bSet; +} + +void ScDPObject::SetSaveData(const ScDPSaveData& rData) +{ + if ( pSaveData.get() != &rData ) // API implementation modifies the original SaveData object + { + pSaveData.reset( new ScDPSaveData( rData ) ); + } + + InvalidateData(); // re-init source from SaveData +} + +void ScDPObject::SetHeaderLayout (bool bUseGrid) +{ + mbHeaderLayout = bUseGrid; +} + +void ScDPObject::SetOutRange(const ScRange& rRange) +{ + aOutRange = rRange; + + if ( pOutput ) + pOutput->SetPosition( rRange.aStart ); +} + +const ScRange& ScDPObject::GetOutRange() const +{ + return aOutRange; +} + +void ScDPObject::SetSheetDesc(const ScSheetSourceDesc& rDesc) +{ + if ( pSheetDesc && rDesc == *pSheetDesc ) + return; // nothing to do + + pImpDesc.reset(); + pServDesc.reset(); + + pSheetDesc.reset( new ScSheetSourceDesc(rDesc) ); + + // make valid QueryParam + + const ScRange& rSrcRange = pSheetDesc->GetSourceRange(); + ScQueryParam aParam = pSheetDesc->GetQueryParam(); + aParam.nCol1 = rSrcRange.aStart.Col(); + aParam.nRow1 = rSrcRange.aStart.Row(); + aParam.nCol2 = rSrcRange.aEnd.Col(); + aParam.nRow2 = rSrcRange.aEnd.Row(); + aParam.bHasHeader = true; + pSheetDesc->SetQueryParam(aParam); + + ClearTableData(); // new source must be created +} + +void ScDPObject::SetImportDesc(const ScImportSourceDesc& rDesc) +{ + if ( pImpDesc && rDesc == *pImpDesc ) + return; // nothing to do + + pSheetDesc.reset(); + pServDesc.reset(); + + pImpDesc.reset( new ScImportSourceDesc(rDesc) ); + + ClearTableData(); // new source must be created +} + +void ScDPObject::SetServiceData(const ScDPServiceDesc& rDesc) +{ + if ( pServDesc && rDesc == *pServDesc ) + return; // nothing to do + + pSheetDesc.reset(); + pImpDesc.reset(); + + pServDesc.reset( new ScDPServiceDesc(rDesc) ); + + ClearTableData(); // new source must be created +} + +void ScDPObject::WriteSourceDataTo( ScDPObject& rDest ) const +{ + if ( pSheetDesc ) + rDest.SetSheetDesc( *pSheetDesc ); + else if ( pImpDesc ) + rDest.SetImportDesc( *pImpDesc ); + else if ( pServDesc ) + rDest.SetServiceData( *pServDesc ); + + // name/tag are not source data, but needed along with source data + + rDest.aTableName = aTableName; + rDest.aTableTag = aTableTag; +} + +void ScDPObject::WriteTempDataTo( ScDPObject& rDest ) const +{ + rDest.nHeaderRows = nHeaderRows; +} + +bool ScDPObject::IsSheetData() const +{ + return ( pSheetDesc != nullptr ); +} + +void ScDPObject::SetName(const OUString& rNew) +{ + aTableName = rNew; +} + +void ScDPObject::SetTag(const OUString& rNew) +{ + aTableTag = rNew; +} + +bool ScDPObject::IsDataDescriptionCell(const ScAddress& rPos) +{ + if (!pSaveData) + return false; + + tools::Long nDataDimCount = pSaveData->GetDataDimensionCount(); + if (nDataDimCount != 1) + // There has to be exactly one data dimension for the description to + // appear at top-left corner. + return false; + + CreateOutput(); + ScRange aTabRange = pOutput->GetOutputRange(sheet::DataPilotOutputRangeType::TABLE); + return (rPos == aTabRange.aStart); +} + +uno::Reference<sheet::XDimensionsSupplier> const & ScDPObject::GetSource() +{ + CreateObjects(); + return xSource; +} + +void ScDPObject::CreateOutput() +{ + CreateObjects(); + if (pOutput) + return; + + bool bFilterButton = IsSheetData() && pSaveData && pSaveData->GetFilterButton(); + pOutput.reset( new ScDPOutput( pDoc, xSource, aOutRange.aStart, bFilterButton ) ); + pOutput->SetHeaderLayout ( mbHeaderLayout ); + + sal_Int32 nOldRows = nHeaderRows; + nHeaderRows = pOutput->GetHeaderRows(); + + if ( !(bAllowMove && nHeaderRows != nOldRows) ) + return; + + sal_Int32 nDiff = nOldRows - nHeaderRows; + if ( nOldRows == 0 ) + --nDiff; + if ( nHeaderRows == 0 ) + ++nDiff; + + sal_Int32 nNewRow = aOutRange.aStart.Row() + nDiff; + if ( nNewRow < 0 ) + nNewRow = 0; + + ScAddress aStart( aOutRange.aStart ); + aStart.SetRow(nNewRow); + pOutput->SetPosition( aStart ); + + //TODO: modify aOutRange? + + bAllowMove = false; // use only once +} + +namespace { + +class DisableGetPivotData +{ + ScDPObject& mrDPObj; + bool mbOldState; +public: + DisableGetPivotData(ScDPObject& rObj, bool bOld) : mrDPObj(rObj), mbOldState(bOld) + { + mrDPObj.EnableGetPivotData(false); + } + + ~DisableGetPivotData() + { + mrDPObj.EnableGetPivotData(mbOldState); + } +}; + +class FindIntersectingTable +{ + ScRange maRange; +public: + explicit FindIntersectingTable(const ScRange& rRange) : maRange(rRange) {} + + bool operator() (const std::unique_ptr<ScDPObject>& rObj) const + { + return maRange.Intersects(rObj->GetOutRange()); + } +}; + +class FindIntersectingTableByColumns +{ + SCCOL mnCol1; + SCCOL mnCol2; + SCROW mnRow; + SCTAB mnTab; +public: + FindIntersectingTableByColumns(SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab) : + mnCol1(nCol1), mnCol2(nCol2), mnRow(nRow), mnTab(nTab) {} + + bool operator() (const std::unique_ptr<ScDPObject>& rObj) const + { + const ScRange& rRange = rObj->GetOutRange(); + if (mnTab != rRange.aStart.Tab()) + // Not on this sheet. + return false; + + if (rRange.aEnd.Row() < mnRow) + // This table is above the row. It's safe. + return false; + + if (mnCol1 <= rRange.aStart.Col() && rRange.aEnd.Col() <= mnCol2) + // This table is fully enclosed in this column range. + return false; + + if (rRange.aEnd.Col() < mnCol1 || mnCol2 < rRange.aStart.Col()) + // This table is entirely outside this column range. + return false; + + // This table must be intersected by this column range. + return true; + } +}; + +class FindIntersectingTableByRows +{ + SCCOL mnCol; + SCROW mnRow1; + SCROW mnRow2; + SCTAB mnTab; +public: + FindIntersectingTableByRows(SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab) : + mnCol(nCol), mnRow1(nRow1), mnRow2(nRow2), mnTab(nTab) {} + + bool operator() (const std::unique_ptr<ScDPObject>& rObj) const + { + const ScRange& rRange = rObj->GetOutRange(); + if (mnTab != rRange.aStart.Tab()) + // Not on this sheet. + return false; + + if (rRange.aEnd.Col() < mnCol) + // This table is to the left of the column. It's safe. + return false; + + if (mnRow1 <= rRange.aStart.Row() && rRange.aEnd.Row() <= mnRow2) + // This table is fully enclosed in this row range. + return false; + + if (rRange.aEnd.Row() < mnRow1 || mnRow2 < rRange.aStart.Row()) + // This table is entirely outside this row range. + return false; + + // This table must be intersected by this row range. + return true; + } +}; + +class AccumulateOutputRanges +{ + ScRangeList maRanges; + SCTAB mnTab; +public: + explicit AccumulateOutputRanges(SCTAB nTab) : mnTab(nTab) {} + AccumulateOutputRanges(const AccumulateOutputRanges& r) : maRanges(r.maRanges), mnTab(r.mnTab) {} + + void operator() (const std::unique_ptr<ScDPObject>& rObj) + { + const ScRange& rRange = rObj->GetOutRange(); + if (mnTab != rRange.aStart.Tab()) + // Not on this sheet. + return; + + maRanges.Join(rRange); + } + + const ScRangeList& getRanges() const { return maRanges; } +}; + +} + +ScDPTableData* ScDPObject::GetTableData() +{ + if (!mpTableData) + { + shared_ptr<ScDPTableData> pData; + const ScDPDimensionSaveData* pDimData = pSaveData ? pSaveData->GetExistingDimensionData() : nullptr; + + if ( pImpDesc ) + { + // database data + const ScDPCache* pCache = pImpDesc->CreateCache(pDimData); + if (pCache) + { + pCache->AddReference(this); + pData = std::make_shared<ScDatabaseDPData>(pDoc, *pCache); + } + } + else + { + // cell data + if (!pSheetDesc) + { + OSL_FAIL("no source descriptor"); + pSheetDesc.reset( new ScSheetSourceDesc(pDoc) ); // dummy defaults + } + + { + // Temporarily disable GETPIVOTDATA to avoid having + // GETPIVOTDATA called onto itself from within the source + // range. + DisableGetPivotData aSwitch(*this, mbEnableGetPivotData); + const ScDPCache* pCache = pSheetDesc->CreateCache(pDimData); + if (pCache) + { + pCache->AddReference(this); + pData = std::make_shared<ScSheetDPData>(pDoc, *pSheetDesc, *pCache); + } + } + } + + // grouping (for cell or database data) + if (pData && pDimData) + { + auto pGroupData = std::make_shared<ScDPGroupTableData>(pData, pDoc); + pDimData->WriteToData(*pGroupData); + pData = pGroupData; + } + + mpTableData = pData; // after SetCacheId + } + + return mpTableData.get(); +} + +void ScDPObject::CreateObjects() +{ + if (!xSource.is()) + { + pOutput.reset(); // not valid when xSource is changed + + if ( pServDesc ) + { + xSource = CreateSource( *pServDesc ); + } + + if ( !xSource.is() ) // database or sheet data, or error in CreateSource + { + OSL_ENSURE( !pServDesc, "DPSource could not be created" ); + ScDPTableData* pData = GetTableData(); + if (pData) + { + if (pSaveData) + // Make sure to transfer these flags to the table data + // since they may have changed. + pData->SetEmptyFlags(pSaveData->GetIgnoreEmptyRows(), pSaveData->GetRepeatIfEmpty()); + + pData->ReloadCacheTable(); + xSource = new ScDPSource( pData ); + } + } + + if (pSaveData) + pSaveData->WriteToSource( xSource ); + } + else if (bSettingsChanged) + { + pOutput.reset(); // not valid when xSource is changed + + uno::Reference<util::XRefreshable> xRef( xSource, uno::UNO_QUERY ); + if (xRef.is()) + { + try + { + xRef->refresh(); + } + catch(uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sc", "exception in refresh"); + } + } + + if (pSaveData) + pSaveData->WriteToSource( xSource ); + } + bSettingsChanged = false; +} + +void ScDPObject::InvalidateData() +{ + bSettingsChanged = true; +} + +void ScDPObject::Clear() +{ + pOutput.reset(); + pSaveData.reset(); + pSheetDesc.reset(); + pImpDesc.reset(); + pServDesc.reset(); + ClearTableData(); + maInteropGrabBag.clear(); +} + +void ScDPObject::ClearTableData() +{ + ClearSource(); + + if (mpTableData) + mpTableData->GetCacheTable().getCache().RemoveReference(this); + mpTableData.reset(); +} + +void ScDPObject::ReloadGroupTableData() +{ + ClearSource(); + + if (!mpTableData) + // Table data not built yet. No need to reload the group data. + return; + + if (!pSaveData) + // How could it not have the save data... but whatever. + return; + + const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData(); + if (!pDimData || !pDimData->HasGroupDimensions()) + { + // No group dimensions exist. Check if it currently has group + // dimensions, and if so, remove all of them. + ScDPGroupTableData* pData = dynamic_cast<ScDPGroupTableData*>(mpTableData.get()); + if (pData) + { + // Replace the existing group table data with the source data. + mpTableData = pData->GetSourceTableData(); + } + return; + } + + ScDPGroupTableData* pData = dynamic_cast<ScDPGroupTableData*>(mpTableData.get()); + if (pData) + { + // This is already a group table data. Salvage the source data and + // re-create a new group data. + const shared_ptr<ScDPTableData>& pSource = pData->GetSourceTableData(); + auto pGroupData = std::make_shared<ScDPGroupTableData>(pSource, pDoc); + pDimData->WriteToData(*pGroupData); + mpTableData = pGroupData; + } + else + { + // This is a source data. Create a group data based on it. + auto pGroupData = std::make_shared<ScDPGroupTableData>(mpTableData, pDoc); + pDimData->WriteToData(*pGroupData); + mpTableData = pGroupData; + } + + bSettingsChanged = true; +} + +void ScDPObject::ClearSource() +{ + Reference< XComponent > xObjectComp( xSource, UNO_QUERY ); + if (xObjectComp.is()) + { + try + { + xObjectComp->dispose(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sc.core"); + } + } + xSource = nullptr; +} + +ScRange ScDPObject::GetNewOutputRange( bool& rOverflow ) +{ + CreateOutput(); // create xSource and pOutput if not already done + + rOverflow = pOutput->HasError(); // range overflow or exception from source + if ( rOverflow ) + return ScRange( aOutRange.aStart ); + else + { + // don't store the result in aOutRange, because nothing has been output yet + return pOutput->GetOutputRange(); + } +} + +void ScDPObject::Output( const ScAddress& rPos ) +{ + // clear old output area + pDoc->DeleteAreaTab( aOutRange.aStart.Col(), aOutRange.aStart.Row(), + aOutRange.aEnd.Col(), aOutRange.aEnd.Row(), + aOutRange.aStart.Tab(), InsertDeleteFlags::ALL ); + pDoc->RemoveFlagsTab( aOutRange.aStart.Col(), aOutRange.aStart.Row(), + aOutRange.aEnd.Col(), aOutRange.aEnd.Row(), + aOutRange.aStart.Tab(), ScMF::Auto ); + + CreateOutput(); // create xSource and pOutput if not already done + + pOutput->SetPosition( rPos ); + + pOutput->Output(); + + // aOutRange is always the range that was last output to the document + aOutRange = pOutput->GetOutputRange(); + const ScAddress& s = aOutRange.aStart; + const ScAddress& e = aOutRange.aEnd; + pDoc->ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable); +} + +ScRange ScDPObject::GetOutputRangeByType( sal_Int32 nType ) +{ + CreateOutput(); + + if (pOutput->HasError()) + return ScRange(aOutRange.aStart); + + return pOutput->GetOutputRange(nType); +} + +ScRange ScDPObject::GetOutputRangeByType( sal_Int32 nType ) const +{ + if (!pOutput || pOutput->HasError()) + return ScRange(ScAddress::INITIALIZE_INVALID); + + return pOutput->GetOutputRange(nType); +} + +static bool lcl_HasButton( const ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + return pDoc->GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG )->HasPivotButton(); +} + +void ScDPObject::RefreshAfterLoad() +{ + // apply drop-down attribute, initialize nHeaderRows, without accessing the source + // (button attribute must be present) + + // simple test: block of button cells at the top, followed by an empty cell + + SCCOL nFirstCol = aOutRange.aStart.Col(); + SCROW nFirstRow = aOutRange.aStart.Row(); + SCTAB nTab = aOutRange.aStart.Tab(); + + SCROW nInitial = 0; + SCROW nOutRows = aOutRange.aEnd.Row() + 1 - aOutRange.aStart.Row(); + while ( nInitial + 1 < nOutRows && lcl_HasButton( pDoc, nFirstCol, nFirstRow + nInitial, nTab ) ) + ++nInitial; + + if ( nInitial + 1 < nOutRows && + pDoc->IsBlockEmpty( nFirstCol, nFirstRow + nInitial, nFirstCol, nFirstRow + nInitial, nTab ) && + aOutRange.aEnd.Col() > nFirstCol ) + { + nHeaderRows = nInitial; + } + else + nHeaderRows = 0; // nothing found, no drop-down lists +} + +void ScDPObject::BuildAllDimensionMembers() +{ + if (!pSaveData) + return; + + // #i111857# don't always create empty mpTableData for external service. + if (pServDesc) + return; + + ScDPTableData* pTableData = GetTableData(); + if(pTableData) + pSaveData->BuildAllDimensionMembers(pTableData); +} + +bool ScDPObject::SyncAllDimensionMembers() +{ + if (!pSaveData) + return false; + + // #i111857# don't always create empty mpTableData for external service. + // Ideally, xSource should be used instead of mpTableData. + if (pServDesc) + return false; + + ScDPTableData* pData = GetTableData(); + if (!pData) + // No table data exists. This can happen when refreshing from an + // external source which doesn't exist. + return false; + + // Refresh the cache wrapper since the cache may have changed. + pData->SetEmptyFlags(pSaveData->GetIgnoreEmptyRows(), pSaveData->GetRepeatIfEmpty()); + pData->ReloadCacheTable(); + pSaveData->SyncAllDimensionMembers(pData); + return true; +} + +bool ScDPObject::GetMemberNames( sal_Int32 nDim, Sequence<OUString>& rNames ) +{ + vector<ScDPLabelData::Member> aMembers; + if (!GetMembers(nDim, GetUsedHierarchy(nDim), aMembers)) + return false; + + size_t n = aMembers.size(); + rNames.realloc(n); + auto pNames = rNames.getArray(); + for (size_t i = 0; i < n; ++i) + pNames[i] = aMembers[i].maName; + + return true; +} + +bool ScDPObject::GetMembers( sal_Int32 nDim, sal_Int32 nHier, vector<ScDPLabelData::Member>& rMembers ) +{ + Reference< sheet::XMembersAccess > xMembersNA; + if (!GetMembersNA( nDim, nHier, xMembersNA )) + return false; + + Reference<container::XIndexAccess> xMembersIA( new ScNameToIndexAccess(xMembersNA) ); + sal_Int32 nCount = xMembersIA->getCount(); + vector<ScDPLabelData::Member> aMembers; + aMembers.reserve(nCount); + + for (sal_Int32 i = 0; i < nCount; ++i) + { + Reference<container::XNamed> xMember; + try + { + xMember = Reference<container::XNamed>(xMembersIA->getByIndex(i), UNO_QUERY); + } + catch (const container::NoSuchElementException&) + { + TOOLS_WARN_EXCEPTION("sc", "ScNameToIndexAccess getByIndex failed"); + } + + ScDPLabelData::Member aMem; + + if (xMember.is()) + aMem.maName = xMember->getName(); + + Reference<beans::XPropertySet> xMemProp(xMember, UNO_QUERY); + if (xMemProp.is()) + { + aMem.mbVisible = ScUnoHelpFunctions::GetBoolProperty(xMemProp, SC_UNO_DP_ISVISIBLE); + aMem.mbShowDetails = ScUnoHelpFunctions::GetBoolProperty(xMemProp, SC_UNO_DP_SHOWDETAILS); + + aMem.maLayoutName = ScUnoHelpFunctions::GetStringProperty( + xMemProp, SC_UNO_DP_LAYOUTNAME, OUString()); + } + + aMembers.push_back(aMem); + } + rMembers.swap(aMembers); + return true; +} + +void ScDPObject::UpdateReference( UpdateRefMode eUpdateRefMode, + const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + // Output area + + SCCOL nCol1 = aOutRange.aStart.Col(); + SCROW nRow1 = aOutRange.aStart.Row(); + SCTAB nTab1 = aOutRange.aStart.Tab(); + SCCOL nCol2 = aOutRange.aEnd.Col(); + SCROW nRow2 = aOutRange.aEnd.Row(); + SCTAB nTab2 = aOutRange.aEnd.Tab(); + + ScRefUpdateRes eRes = + ScRefUpdate::Update( pDoc, eUpdateRefMode, + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( eRes != UR_NOTHING ) + SetOutRange( ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ) ); + + // sheet source data + + if ( !pSheetDesc ) + return; + + const OUString& rRangeName = pSheetDesc->GetRangeName(); + if (!rRangeName.isEmpty()) + // Source range is a named range. No need to update. + return; + + const ScRange& rSrcRange = pSheetDesc->GetSourceRange(); + nCol1 = rSrcRange.aStart.Col(); + nRow1 = rSrcRange.aStart.Row(); + nTab1 = rSrcRange.aStart.Tab(); + nCol2 = rSrcRange.aEnd.Col(); + nRow2 = rSrcRange.aEnd.Row(); + nTab2 = rSrcRange.aEnd.Tab(); + + eRes = ScRefUpdate::Update( pDoc, eUpdateRefMode, + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( eRes == UR_NOTHING ) + return; + + SCCOL nDiffX = nCol1 - pSheetDesc->GetSourceRange().aStart.Col(); + SCROW nDiffY = nRow1 - pSheetDesc->GetSourceRange().aStart.Row(); + + ScQueryParam aParam = pSheetDesc->GetQueryParam(); + aParam.nCol1 = sal::static_int_cast<SCCOL>( aParam.nCol1 + nDiffX ); + aParam.nCol2 = sal::static_int_cast<SCCOL>( aParam.nCol2 + nDiffX ); + aParam.nRow1 += nDiffY; //TODO: used? + aParam.nRow2 += nDiffY; //TODO: used? + SCSIZE nEC = aParam.GetEntryCount(); + for (SCSIZE i=0; i<nEC; i++) + if (aParam.GetEntry(i).bDoQuery) + aParam.GetEntry(i).nField += nDiffX; + + pSheetDesc->SetQueryParam(aParam); + pSheetDesc->SetSourceRange(ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2)); +} + +bool ScDPObject::RefsEqual( const ScDPObject& r ) const +{ + if ( aOutRange != r.aOutRange ) + return false; + + if ( pSheetDesc && r.pSheetDesc ) + { + if ( pSheetDesc->GetSourceRange() != r.pSheetDesc->GetSourceRange() ) + return false; + } + else if ( pSheetDesc || r.pSheetDesc ) + { + OSL_FAIL("RefsEqual: SheetDesc set at only one object"); + return false; + } + + return true; +} + +void ScDPObject::WriteRefsTo( ScDPObject& r ) const +{ + r.SetOutRange( aOutRange ); + if ( pSheetDesc ) + r.SetSheetDesc( *pSheetDesc ); +} + +void ScDPObject::GetPositionData(const ScAddress& rPos, DataPilotTablePositionData& rPosData) +{ + CreateOutput(); + pOutput->GetPositionData(rPos, rPosData); +} + +bool ScDPObject::GetDataFieldPositionData( + const ScAddress& rPos, Sequence<sheet::DataPilotFieldFilter>& rFilters) +{ + CreateOutput(); + + vector<sheet::DataPilotFieldFilter> aFilters; + if (!pOutput->GetDataResultPositionData(aFilters, rPos)) + return false; + + sal_Int32 n = static_cast<sal_Int32>(aFilters.size()); + rFilters.realloc(n); + auto pFilters = rFilters.getArray(); + for (sal_Int32 i = 0; i < n; ++i) + pFilters[i] = aFilters[i]; + + return true; +} + +void ScDPObject::GetDrillDownData(const ScAddress& rPos, Sequence< Sequence<Any> >& rTableData) +{ + CreateOutput(); + + Reference<sheet::XDrillDownDataSupplier> xDrillDownData(xSource, UNO_QUERY); + if (!xDrillDownData.is()) + return; + + Sequence<sheet::DataPilotFieldFilter> filters; + if (!GetDataFieldPositionData(rPos, filters)) + return; + + rTableData = xDrillDownData->getDrillDownData(filters); +} + +bool ScDPObject::IsDimNameInUse(std::u16string_view rName) const +{ + if (!xSource.is()) + return false; + + Reference<container::XNameAccess> xDims = xSource->getDimensions(); + const Sequence<OUString> aDimNames = xDims->getElementNames(); + for (const OUString& rDimName : aDimNames) + { + if (rDimName.equalsIgnoreAsciiCase(rName)) + return true; + + Reference<beans::XPropertySet> xPropSet(xDims->getByName(rDimName), UNO_QUERY); + if (!xPropSet.is()) + continue; + + OUString aLayoutName = ScUnoHelpFunctions::GetStringProperty( + xPropSet, SC_UNO_DP_LAYOUTNAME, OUString()); + if (aLayoutName.equalsIgnoreAsciiCase(rName)) + return true; + } + return false; +} + +OUString ScDPObject::GetDimName( tools::Long nDim, bool& rIsDataLayout, sal_Int32* pFlags ) +{ + rIsDataLayout = false; + OUString aRet; + + if ( xSource.is() ) + { + uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions(); + uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nDimCount = xDims->getCount(); + if ( nDim < nDimCount ) + { + uno::Reference<uno::XInterface> xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference<container::XNamed> xDimName( xIntDim, uno::UNO_QUERY ); + uno::Reference<beans::XPropertySet> xDimProp( xIntDim, uno::UNO_QUERY ); + if ( xDimName.is() && xDimProp.is() ) + { + bool bData = ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ); + //TODO: error checking -- is "IsDataLayoutDimension" property required?? + + OUString aName; + try + { + aName = xDimName->getName(); + } + catch(uno::Exception&) + { + } + if ( bData ) + rIsDataLayout = true; + else + aRet = aName; + + if (pFlags) + *pFlags = ScUnoHelpFunctions::GetLongProperty( xDimProp, + SC_UNO_DP_FLAGS ); + } + } + } + else if (ScDPTableData* pData = GetTableData()) + { + aRet = pData->getDimensionName(nDim); + rIsDataLayout = pData->getIsDataLayoutDimension(nDim); + } + + return aRet; +} + +bool ScDPObject::IsDuplicated( tools::Long nDim ) +{ + bool bDuplicated = false; + if ( xSource.is() ) + { + uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions(); + uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nDimCount = xDims->getCount(); + if ( nDim < nDimCount ) + { + uno::Reference<beans::XPropertySet> xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY); + if ( xDimProp.is() ) + { + try + { + uno::Any aOrigAny = xDimProp->getPropertyValue( SC_UNO_DP_ORIGINAL ); + uno::Reference<uno::XInterface> xIntOrig; + if ( (aOrigAny >>= xIntOrig) && xIntOrig.is() ) + bDuplicated = true; + } + catch(uno::Exception&) + { + } + } + } + } + return bDuplicated; +} + +tools::Long ScDPObject::GetDimCount() +{ + tools::Long nRet = 0; + if ( xSource.is() ) + { + try + { + uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions(); + if ( xDimsName.is() ) + nRet = xDimsName->getElementNames().getLength(); + } + catch(uno::Exception&) + { + } + } + return nRet; +} + +void ScDPObject::GetHeaderPositionData(const ScAddress& rPos, DataPilotTableHeaderData& rData) +{ + CreateOutput(); // create xSource and pOutput if not already done + + // Reset member values to invalid state. + rData.Dimension = rData.Hierarchy = rData.Level = -1; + rData.Flags = 0; + + DataPilotTablePositionData aPosData; + pOutput->GetPositionData(rPos, aPosData); + const sal_Int32 nPosType = aPosData.PositionType; + if (nPosType == css::sheet::DataPilotTablePositionType::COLUMN_HEADER || nPosType == css::sheet::DataPilotTablePositionType::ROW_HEADER) + aPosData.PositionData >>= rData; +} + +namespace { + +class FindByName +{ + OUString maName; // must be all uppercase. +public: + explicit FindByName(const OUString& rName) : maName(rName) {} + bool operator() (const ScDPSaveDimension* pDim) const + { + // Layout name takes precedence. + const std::optional<OUString> & pLayoutName = pDim->GetLayoutName(); + if (pLayoutName && ScGlobal::getCharClass().uppercase(*pLayoutName) == maName) + return true; + + ScGeneralFunction eGenFunc = pDim->GetFunction(); + ScSubTotalFunc eFunc = ScDPUtil::toSubTotalFunc(eGenFunc); + OUString aSrcName = ScDPUtil::getSourceDimensionName(pDim->GetName()); + OUString aFuncName = ScDPUtil::getDisplayedMeasureName(aSrcName, eFunc); + if (maName == ScGlobal::getCharClass().uppercase(aFuncName)) + return true; + + return maName == ScGlobal::getCharClass().uppercase(aSrcName); + } +}; + +class LessByDimOrder +{ + const ScDPSaveData::DimOrderType& mrDimOrder; + +public: + explicit LessByDimOrder(const ScDPSaveData::DimOrderType& rDimOrder) : mrDimOrder(rDimOrder) {} + + bool operator() (const sheet::DataPilotFieldFilter& r1, const sheet::DataPilotFieldFilter& r2) const + { + size_t nRank1 = mrDimOrder.size(); + size_t nRank2 = mrDimOrder.size(); + ScDPSaveData::DimOrderType::const_iterator it1 = mrDimOrder.find( + ScGlobal::getCharClass().uppercase(r1.FieldName)); + if (it1 != mrDimOrder.end()) + nRank1 = it1->second; + + ScDPSaveData::DimOrderType::const_iterator it2 = mrDimOrder.find( + ScGlobal::getCharClass().uppercase(r2.FieldName)); + if (it2 != mrDimOrder.end()) + nRank2 = it2->second; + + return nRank1 < nRank2; + } +}; + +} + +double ScDPObject::GetPivotData(const OUString& rDataFieldName, std::vector<sheet::DataPilotFieldFilter>& rFilters) +{ + if (!mbEnableGetPivotData) + return std::numeric_limits<double>::quiet_NaN(); + + CreateObjects(); + + std::vector<const ScDPSaveDimension*> aDataDims; + pSaveData->GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_DATA, aDataDims); + if (aDataDims.empty()) + return std::numeric_limits<double>::quiet_NaN(); + + std::vector<const ScDPSaveDimension*>::iterator it = std::find_if( + aDataDims.begin(), aDataDims.end(), + FindByName(ScGlobal::getCharClass().uppercase(rDataFieldName))); + + if (it == aDataDims.end()) + return std::numeric_limits<double>::quiet_NaN(); + + size_t nDataIndex = std::distance(aDataDims.begin(), it); + + uno::Reference<sheet::XDataPilotResults> xDPResults(xSource, uno::UNO_QUERY); + if (!xDPResults.is()) + return std::numeric_limits<double>::quiet_NaN(); + + // Dimensions must be sorted in order of appearance, and row dimensions + // must come before column dimensions. + std::sort(rFilters.begin(), rFilters.end(), LessByDimOrder(pSaveData->GetDimensionSortOrder())); + + size_t n = rFilters.size(); + uno::Sequence<sheet::DataPilotFieldFilter> aFilters(n); + auto aFiltersRange = asNonConstRange(aFilters); + for (size_t i = 0; i < n; ++i) + aFiltersRange[i] = rFilters[i]; + + uno::Sequence<double> aRes = xDPResults->getFilteredResults(aFilters); + if (nDataIndex >= o3tl::make_unsigned(aRes.getLength())) + return std::numeric_limits<double>::quiet_NaN(); + + return aRes[nDataIndex]; +} + +bool ScDPObject::IsFilterButton( const ScAddress& rPos ) +{ + CreateOutput(); // create xSource and pOutput if not already done + + return pOutput->IsFilterButton( rPos ); +} + +tools::Long ScDPObject::GetHeaderDim( const ScAddress& rPos, sheet::DataPilotFieldOrientation& rOrient ) +{ + CreateOutput(); // create xSource and pOutput if not already done + + return pOutput->GetHeaderDim( rPos, rOrient ); +} + +bool ScDPObject::GetHeaderDrag( const ScAddress& rPos, bool bMouseLeft, bool bMouseTop, tools::Long nDragDim, + tools::Rectangle& rPosRect, sheet::DataPilotFieldOrientation& rOrient, tools::Long& rDimPos ) +{ + CreateOutput(); // create xSource and pOutput if not already done + + return pOutput->GetHeaderDrag( rPos, bMouseLeft, bMouseTop, nDragDim, rPosRect, rOrient, rDimPos ); +} + +void ScDPObject::GetMemberResultNames(ScDPUniqueStringSet& rNames, tools::Long nDimension) +{ + CreateOutput(); // create xSource and pOutput if not already done + + pOutput->GetMemberResultNames(rNames, nDimension); // used only with table data -> level not needed +} + +OUString ScDPObject::GetFormattedString(std::u16string_view rDimName, const double fValue) +{ + ScDPTableData* pTableData = GetTableData(); + if(!pTableData) + return OUString(); + + tools::Long nDim; + for (nDim = 0; nDim < pTableData->GetColumnCount(); ++nDim) + { + if(rDimName == pTableData->getDimensionName(nDim)) + break; + } + ScDPItemData aItemData; + aItemData.SetValue(fValue); + return GetTableData()->GetFormattedString(nDim, aItemData, false); +} + + +namespace { + +bool dequote( const OUString& rSource, sal_Int32 nStartPos, sal_Int32& rEndPos, OUString& rResult ) +{ + // nStartPos has to point to opening quote + + const sal_Unicode cQuote = '\''; + + if (rSource[nStartPos] == cQuote) + { + OUStringBuffer aBuffer; + sal_Int32 nPos = nStartPos + 1; + const sal_Int32 nLen = rSource.getLength(); + + while ( nPos < nLen ) + { + const sal_Unicode cNext = rSource[nPos]; + if ( cNext == cQuote ) + { + if (nPos+1 < nLen && rSource[nPos+1] == cQuote) + { + // double quote is used for an embedded quote + aBuffer.append( cNext ); // append one quote + ++nPos; // skip the next one + } + else + { + // end of quoted string + rResult = aBuffer.makeStringAndClear(); + rEndPos = nPos + 1; // behind closing quote + return true; + } + } + else + aBuffer.append( cNext ); + + ++nPos; + } + // no closing quote before the end of the string -> error (bRet still false) + } + + return false; +} + +struct ScGetPivotDataFunctionEntry +{ + const char* pName; + sal_Int16 eFunc; +}; + +bool parseFunction( const OUString& rList, sal_Int32 nStartPos, sal_Int32& rEndPos, sal_Int16& rFunc ) +{ + static const ScGetPivotDataFunctionEntry aFunctions[] = + { + // our names + { "Sum", sheet::GeneralFunction2::SUM }, + { "Count", sheet::GeneralFunction2::COUNT }, + { "Average", sheet::GeneralFunction2::AVERAGE }, + { "Max", sheet::GeneralFunction2::MAX }, + { "Min", sheet::GeneralFunction2::MIN }, + { "Product", sheet::GeneralFunction2::PRODUCT }, + { "CountNums", sheet::GeneralFunction2::COUNTNUMS }, + { "StDev", sheet::GeneralFunction2::STDEV }, + { "StDevp", sheet::GeneralFunction2::STDEVP }, + { "Var", sheet::GeneralFunction2::VAR }, + { "VarP", sheet::GeneralFunction2::VARP }, + // compatibility names + { "Count Nums", sheet::GeneralFunction2::COUNTNUMS }, + { "StdDev", sheet::GeneralFunction2::STDEV }, + { "StdDevp", sheet::GeneralFunction2::STDEVP } + }; + + const sal_Int32 nListLen = rList.getLength(); + while (nStartPos < nListLen && rList[nStartPos] == ' ') + ++nStartPos; + + bool bParsed = false; + bool bFound = false; + OUString aFuncStr; + sal_Int32 nFuncEnd = 0; + if (nStartPos < nListLen && rList[nStartPos] == '\'') + bParsed = dequote( rList, nStartPos, nFuncEnd, aFuncStr ); + else + { + nFuncEnd = rList.indexOf(']', nStartPos); + if (nFuncEnd >= 0) + { + aFuncStr = rList.copy(nStartPos, nFuncEnd - nStartPos); + bParsed = true; + } + } + + if ( bParsed ) + { + aFuncStr = comphelper::string::strip(aFuncStr, ' '); + + const sal_Int32 nFuncCount = SAL_N_ELEMENTS(aFunctions); + for ( sal_Int32 nFunc=0; nFunc<nFuncCount && !bFound; nFunc++ ) + { + if (aFuncStr.equalsIgnoreAsciiCaseAscii(aFunctions[nFunc].pName)) + { + rFunc = aFunctions[nFunc].eFunc; + bFound = true; + + while (nFuncEnd < nListLen && rList[nFuncEnd] == ' ') + ++nFuncEnd; + rEndPos = nFuncEnd; + } + } + } + + return bFound; +} + +bool extractAtStart( const OUString& rList, sal_Int32& rMatched, bool bAllowBracket, sal_Int16* pFunc, + OUString& rDequoted ) +{ + sal_Int32 nMatchList = 0; + sal_Unicode cFirst = rList[0]; + bool bParsed = false; + if ( cFirst == '\'' || cFirst == '[' ) + { + // quoted string or string in brackets must match completely + + OUString aDequoted; + sal_Int32 nQuoteEnd = 0; + + if ( cFirst == '\'' ) + bParsed = dequote( rList, 0, nQuoteEnd, aDequoted ); + else if ( cFirst == '[' ) + { + // skip spaces after the opening bracket + + sal_Int32 nStartPos = 1; + const sal_Int32 nListLen = rList.getLength(); + while (nStartPos < nListLen && rList[nStartPos] == ' ') + ++nStartPos; + + if (nStartPos < nListLen && rList[nStartPos] == '\'') // quoted within the brackets? + { + if ( dequote( rList, nStartPos, nQuoteEnd, aDequoted ) ) + { + // after the quoted string, there must be the closing bracket, optionally preceded by spaces, + // and/or a function name + while (nQuoteEnd < nListLen && rList[nQuoteEnd] == ' ') + ++nQuoteEnd; + + // semicolon separates function name + if (nQuoteEnd < nListLen && rList[nQuoteEnd] == ';' && pFunc) + { + sal_Int32 nFuncEnd = 0; + if ( parseFunction( rList, nQuoteEnd + 1, nFuncEnd, *pFunc ) ) + nQuoteEnd = nFuncEnd; + } + if (nQuoteEnd < nListLen && rList[nQuoteEnd] == ']') + { + ++nQuoteEnd; // include the closing bracket for the matched length + bParsed = true; + } + } + } + else + { + // implicit quoting to the closing bracket + + sal_Int32 nClosePos = rList.indexOf(']', nStartPos); + if (nClosePos >= 0) + { + sal_Int32 nNameEnd = nClosePos; + sal_Int32 nSemiPos = rList.indexOf(';', nStartPos); + if (nSemiPos >= 0 && nSemiPos < nClosePos && pFunc) + { + sal_Int32 nFuncEnd = 0; + if (parseFunction(rList, nSemiPos+1, nFuncEnd, *pFunc)) + nNameEnd = nSemiPos; + } + + aDequoted = rList.copy(nStartPos, nNameEnd - nStartPos); + // spaces before the closing bracket or semicolon + aDequoted = comphelper::string::stripEnd(aDequoted, ' '); + nQuoteEnd = nClosePos + 1; + bParsed = true; + } + } + } + + if ( bParsed ) + { + nMatchList = nQuoteEnd; // match count in the list string, including quotes + rDequoted = aDequoted; + } + } + + if (bParsed) + { + // look for following space or end of string + + bool bValid = false; + if ( sal::static_int_cast<sal_Int32>(nMatchList) >= rList.getLength() ) + bValid = true; + else + { + sal_Unicode cNext = rList[nMatchList]; + if ( cNext == ' ' || ( bAllowBracket && cNext == '[' ) ) + bValid = true; + } + + if ( bValid ) + { + rMatched = nMatchList; + return true; + } + } + + return false; +} + +bool isAtStart( + const OUString& rList, const OUString& rSearch, sal_Int32& rMatched, + bool bAllowBracket, sal_Int16* pFunc ) +{ + sal_Int32 nMatchList = 0; + sal_Int32 nMatchSearch = 0; + sal_Unicode cFirst = rList[0]; + if ( cFirst == '\'' || cFirst == '[' ) + { + OUString aDequoted; + bool bParsed = extractAtStart( rList, rMatched, bAllowBracket, pFunc, aDequoted); + if ( bParsed && ScGlobal::GetTransliteration().isEqual( aDequoted, rSearch ) ) + { + nMatchList = rMatched; // match count in the list string, including quotes + nMatchSearch = rSearch.getLength(); + } + } + else + { + // otherwise look for search string at the start of rList + ScGlobal::GetTransliteration().equals( + rList, 0, rList.getLength(), nMatchList, rSearch, 0, rSearch.getLength(), nMatchSearch); + } + + if (nMatchSearch == rSearch.getLength()) + { + // search string is at start of rList - look for following space or end of string + + bool bValid = false; + if ( sal::static_int_cast<sal_Int32>(nMatchList) >= rList.getLength() ) + bValid = true; + else + { + sal_Unicode cNext = rList[nMatchList]; + if ( cNext == ' ' || ( bAllowBracket && cNext == '[' ) ) + bValid = true; + } + + if ( bValid ) + { + rMatched = nMatchList; + return true; + } + } + + return false; +} + +} // anonymous namespace + +bool ScDPObject::ParseFilters( + OUString& rDataFieldName, + std::vector<sheet::DataPilotFieldFilter>& rFilters, + std::vector<sal_Int16>& rFilterFuncs, std::u16string_view rFilterList ) +{ + // parse the string rFilterList into parameters for GetPivotData + + CreateObjects(); // create xSource if not already done + + std::vector<OUString> aDataNames; // data fields (source name) + std::vector<OUString> aGivenNames; // data fields (compound name) + std::vector<OUString> aFieldNames; // column/row/data fields + std::vector< uno::Sequence<OUString> > aFieldValueNames; + std::vector< uno::Sequence<OUString> > aFieldValues; + + // get all the field and item names + + uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions(); + uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName ); + sal_Int32 nDimCount = xIntDims->getCount(); + for ( sal_Int32 nDim = 0; nDim<nDimCount; nDim++ ) + { + uno::Reference<uno::XInterface> xIntDim(xIntDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference<container::XNamed> xDim( xIntDim, uno::UNO_QUERY ); + uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY ); + uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDim, uno::UNO_QUERY ); + bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ); + sheet::DataPilotFieldOrientation nOrient = ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + if ( !bDataLayout ) + { + if ( nOrient == sheet::DataPilotFieldOrientation_DATA ) + { + OUString aSourceName; + OUString aGivenName; + ScDPOutput::GetDataDimensionNames( aSourceName, aGivenName, xIntDim ); + aDataNames.push_back( aSourceName ); + aGivenNames.push_back( aGivenName ); + } + else if ( nOrient != sheet::DataPilotFieldOrientation_HIDDEN ) + { + // get level names, as in ScDPOutput + + uno::Reference<container::XIndexAccess> xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + sal_Int32 nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp, + SC_UNO_DP_USEDHIERARCHY ); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + + uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if ( xHierSupp.is() ) + { + uno::Reference<container::XIndexAccess> xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); + sal_Int32 nLevCount = xLevels->getCount(); + for (sal_Int32 nLev=0; nLev<nLevCount; nLev++) + { + uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLev), + uno::UNO_QUERY); + uno::Reference<container::XNamed> xLevNam( xLevel, uno::UNO_QUERY ); + uno::Reference<sheet::XMembersSupplier> xLevSupp( xLevel, uno::UNO_QUERY ); + if ( xLevNam.is() && xLevSupp.is() ) + { + uno::Reference<sheet::XMembersAccess> xMembers = xLevSupp->getMembers(); + + OUString aFieldName( xLevNam->getName() ); + // getElementNames() and getLocaleIndependentElementNames() + // must be consecutive calls to obtain strings in matching order. + uno::Sequence<OUString> aMemberValueNames( xMembers->getElementNames() ); + uno::Sequence<OUString> aMemberValues( xMembers->getLocaleIndependentElementNames() ); + + aFieldNames.push_back( aFieldName ); + aFieldValueNames.push_back( aMemberValueNames ); + aFieldValues.push_back( aMemberValues ); + } + } + } + } + } + } + + // compare and build filters + + SCSIZE nDataFields = aDataNames.size(); + SCSIZE nFieldCount = aFieldNames.size(); + OSL_ENSURE( aGivenNames.size() == nDataFields && aFieldValueNames.size() == nFieldCount && + aFieldValues.size() == nFieldCount, "wrong count" ); + + bool bError = false; + bool bHasData = false; + OUString aRemaining(comphelper::string::strip(rFilterList, ' ')); + while (!aRemaining.isEmpty() && !bError) + { + bool bUsed = false; + + // look for data field name + + for ( SCSIZE nDataPos=0; nDataPos<nDataFields && !bUsed; nDataPos++ ) + { + OUString aFound; + sal_Int32 nMatched = 0; + if (isAtStart(aRemaining, aDataNames[nDataPos], nMatched, false, nullptr)) + aFound = aDataNames[nDataPos]; + else if (isAtStart(aRemaining, aGivenNames[nDataPos], nMatched, false, nullptr)) + aFound = aGivenNames[nDataPos]; + + if (!aFound.isEmpty()) + { + rDataFieldName = aFound; + aRemaining = aRemaining.copy(nMatched); + bHasData = true; + bUsed = true; + } + } + + // look for field name + + OUString aSpecField; + bool bHasFieldName = false; + if ( !bUsed ) + { + sal_Int32 nMatched = 0; + for ( SCSIZE nField=0; nField<nFieldCount && !bHasFieldName; nField++ ) + { + if (isAtStart(aRemaining, aFieldNames[nField], nMatched, true, nullptr)) + { + aSpecField = aFieldNames[nField]; + aRemaining = aRemaining.copy(nMatched); + aRemaining = comphelper::string::stripStart(aRemaining, ' '); + + // field name has to be followed by item name in brackets + if (aRemaining.startsWith("[")) + { + bHasFieldName = true; + // bUsed remains false - still need the item + } + else + { + bUsed = true; + bError = true; + } + } + } + } + + // look for field item + + if ( !bUsed ) + { + bool bItemFound = false; + sal_Int32 nMatched = 0; + OUString aFoundName; + OUString aFoundValueName; + OUString aFoundValue; + sal_Int16 eFunc = sheet::GeneralFunction2::NONE; + sal_Int16 eFoundFunc = sheet::GeneralFunction2::NONE; + + OUString aQueryValueName; + const bool bHasQuery = extractAtStart( aRemaining, nMatched, false, &eFunc, aQueryValueName); + + OUString aQueryValue = aQueryValueName; + if (mpTableData) + { + SvNumberFormatter* pFormatter = mpTableData->GetCacheTable().getCache().GetNumberFormatter(); + if (pFormatter) + { + // Parse possible number from aQueryValueName and format + // locale independent as aQueryValue. + sal_uInt32 nNumFormat = 0; + double fValue; + if (pFormatter->IsNumberFormat( aQueryValueName, nNumFormat, fValue)) + aQueryValue = ScDPCache::GetLocaleIndependentFormattedString( fValue, *pFormatter, nNumFormat); + } + } + + for ( SCSIZE nField=0; nField<nFieldCount; nField++ ) + { + // If a field name is given, look in that field only, otherwise in all fields. + // aSpecField is initialized from aFieldNames array, so exact comparison can be used. + if ( !bHasFieldName || aFieldNames[nField] == aSpecField ) + { + const uno::Sequence<OUString>& rItemNames = aFieldValueNames[nField]; + const uno::Sequence<OUString>& rItemValues = aFieldValues[nField]; + sal_Int32 nItemCount = rItemNames.getLength(); + assert(nItemCount == rItemValues.getLength()); + const OUString* pItemNamesArr = rItemNames.getConstArray(); + const OUString* pItemValuesArr = rItemValues.getConstArray(); + for ( sal_Int32 nItem=0; nItem<nItemCount; nItem++ ) + { + bool bThisItemFound; + if (bHasQuery) + { + // First check given value name against both. + bThisItemFound = ScGlobal::GetTransliteration().isEqual( + aQueryValueName, pItemNamesArr[nItem]); + if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem]) + bThisItemFound = ScGlobal::GetTransliteration().isEqual( + aQueryValueName, pItemValuesArr[nItem]); + if (!bThisItemFound && aQueryValueName != aQueryValue) + { + // Second check locale independent value + // against both. + /* TODO: or check only value string against + * value string, not against the value name? */ + bThisItemFound = ScGlobal::GetTransliteration().isEqual( + aQueryValue, pItemNamesArr[nItem]); + if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem]) + bThisItemFound = ScGlobal::GetTransliteration().isEqual( + aQueryValue, pItemValuesArr[nItem]); + } + } + else + { + bThisItemFound = isAtStart( aRemaining, pItemNamesArr[nItem], nMatched, false, &eFunc ); + if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem]) + bThisItemFound = isAtStart( aRemaining, pItemValuesArr[nItem], nMatched, false, &eFunc ); + /* TODO: this checks only the given value name, + * check also locale independent value. But we'd + * have to do that in each iteration of the loop + * inside isAtStart() since a query could not be + * extracted and a match could be on the passed + * item value name string or item value string + * starting at aRemaining. */ + } + if (bThisItemFound) + { + if ( bItemFound ) + bError = true; // duplicate (also across fields) + else + { + aFoundName = aFieldNames[nField]; + aFoundValueName = pItemNamesArr[nItem]; + aFoundValue = pItemValuesArr[nItem]; + eFoundFunc = eFunc; + bItemFound = true; + bUsed = true; + } + } + } + } + } + + if ( bItemFound && !bError ) + { + sheet::DataPilotFieldFilter aField; + aField.FieldName = aFoundName; + aField.MatchValueName = aFoundValueName; + aField.MatchValue = aFoundValue; + rFilters.push_back(aField); + rFilterFuncs.push_back(eFoundFunc); + aRemaining = aRemaining.copy(nMatched); + } + } + + if ( !bUsed ) + bError = true; + + // remove any number of spaces between entries + aRemaining = comphelper::string::stripStart(aRemaining, ' '); + } + + if ( !bError && !bHasData && aDataNames.size() == 1 ) + { + // if there's only one data field, its name need not be specified + rDataFieldName = aDataNames[0]; + bHasData = true; + } + + return bHasData && !bError; +} + +void ScDPObject::ToggleDetails(const DataPilotTableHeaderData& rElemDesc, ScDPObject* pDestObj) +{ + CreateObjects(); // create xSource if not already done + + // find dimension name + + uno::Reference<container::XNamed> xDim; + uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions(); + uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nIntCount = xIntDims->getCount(); + if ( rElemDesc.Dimension < nIntCount ) + { + xDim.set(xIntDims->getByIndex(rElemDesc.Dimension), uno::UNO_QUERY); + } + OSL_ENSURE( xDim.is(), "dimension not found" ); + if ( !xDim.is() ) return; + OUString aDimName = xDim->getName(); + + uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY ); + bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ); + if (bDataLayout) + { + // the elements of the data layout dimension can't be found by their names + // -> don't change anything + return; + } + + // query old state + + tools::Long nHierCount = 0; + uno::Reference<container::XIndexAccess> xHiers; + uno::Reference<sheet::XHierarchiesSupplier> xHierSupp( xDim, uno::UNO_QUERY ); + if ( xHierSupp.is() ) + { + uno::Reference<container::XNameAccess> xHiersName = xHierSupp->getHierarchies(); + xHiers = new ScNameToIndexAccess( xHiersName ); + nHierCount = xHiers->getCount(); + } + uno::Reference<uno::XInterface> xHier; + if ( rElemDesc.Hierarchy < nHierCount ) + xHier.set(xHiers->getByIndex(rElemDesc.Hierarchy), uno::UNO_QUERY); + OSL_ENSURE( xHier.is(), "hierarchy not found" ); + if ( !xHier.is() ) return; + + tools::Long nLevCount = 0; + uno::Reference<container::XIndexAccess> xLevels; + uno::Reference<sheet::XLevelsSupplier> xLevSupp( xHier, uno::UNO_QUERY ); + if ( xLevSupp.is() ) + { + uno::Reference<container::XNameAccess> xLevsName = xLevSupp->getLevels(); + xLevels = new ScNameToIndexAccess( xLevsName ); + nLevCount = xLevels->getCount(); + } + uno::Reference<uno::XInterface> xLevel; + if ( rElemDesc.Level < nLevCount ) + xLevel.set(xLevels->getByIndex(rElemDesc.Level), uno::UNO_QUERY); + OSL_ENSURE( xLevel.is(), "level not found" ); + if ( !xLevel.is() ) return; + + uno::Reference<sheet::XMembersAccess> xMembers; + uno::Reference<sheet::XMembersSupplier> xMbrSupp( xLevel, uno::UNO_QUERY ); + if ( xMbrSupp.is() ) + xMembers = xMbrSupp->getMembers(); + + bool bFound = false; + bool bShowDetails = true; + + if ( xMembers.is() ) + { + if ( xMembers->hasByName(rElemDesc.MemberName) ) + { + uno::Reference<beans::XPropertySet> xMbrProp(xMembers->getByName(rElemDesc.MemberName), + uno::UNO_QUERY); + if ( xMbrProp.is() ) + { + bShowDetails = ScUnoHelpFunctions::GetBoolProperty( xMbrProp, + SC_UNO_DP_SHOWDETAILS ); + //TODO: don't set bFound if property is unknown? + bFound = true; + } + } + } + + OSL_ENSURE( bFound, "member not found" ); + + //TODO: use Hierarchy and Level in SaveData !!!! + + // modify pDestObj if set, this object otherwise + ScDPSaveData* pModifyData = pDestObj ? ( pDestObj->pSaveData.get() ) : pSaveData.get(); + OSL_ENSURE( pModifyData, "no data?" ); + if ( pModifyData ) + { + const OUString aName = rElemDesc.MemberName; + pModifyData->GetDimensionByName(aDimName)-> + GetMemberByName(aName)->SetShowDetails( !bShowDetails ); // toggle + + if ( pDestObj ) + pDestObj->InvalidateData(); // re-init source from SaveData + else + InvalidateData(); // re-init source from SaveData + } +} + +static PivotFunc lcl_FirstSubTotal( const uno::Reference<beans::XPropertySet>& xDimProp ) // PIVOT_FUNC mask +{ + uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDimProp, uno::UNO_QUERY ); + if ( xDimProp.is() && xDimSupp.is() ) + { + uno::Reference<container::XIndexAccess> xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp, + SC_UNO_DP_USEDHIERARCHY ); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + + uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if ( xHierSupp.is() ) + { + uno::Reference<container::XIndexAccess> xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); + uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(0), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xLevProp( xLevel, uno::UNO_QUERY ); + if ( xLevProp.is() ) + { + uno::Any aSubAny; + try + { + aSubAny = xLevProp->getPropertyValue( SC_UNO_DP_SUBTOTAL2 ); + } + catch(uno::Exception&) + { + } + uno::Sequence<sal_Int16> aSeq; + if ( aSubAny >>= aSeq ) + { + PivotFunc nMask = PivotFunc::NONE; + for (const sal_Int16 nElem : std::as_const(aSeq)) + nMask |= ScDataPilotConversion::FunctionBit(nElem); + return nMask; + } + } + } + } + + OSL_FAIL("FirstSubTotal: NULL"); + return PivotFunc::NONE; +} + +namespace { + +class FindByColumn +{ + SCCOL mnCol; + PivotFunc mnMask; +public: + FindByColumn(SCCOL nCol, PivotFunc nMask) : mnCol(nCol), mnMask(nMask) {} + bool operator() (const ScPivotField& r) const + { + return r.nCol == mnCol && r.nFuncMask == mnMask; + } +}; + +} + +static void lcl_FillOldFields( ScPivotFieldVector& rFields, + const uno::Reference<sheet::XDimensionsSupplier>& xSource, + sheet::DataPilotFieldOrientation nOrient, bool bAddData ) +{ + ScPivotFieldVector aFields; + + bool bDataFound = false; + + //TODO: merge multiple occurrences (data field with different functions) + //TODO: force data field in one dimension + + vector<tools::Long> aPos; + + uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions(); + uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nDimCount = xDims->getCount(); + for (tools::Long nDim = 0; nDim < nDimCount; ++nDim) + { + // dimension properties + uno::Reference<beans::XPropertySet> xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY); + + // dimension orientation, hidden by default. + sheet::DataPilotFieldOrientation nDimOrient = ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + + if ( xDimProp.is() && nDimOrient == nOrient ) + { + // Let's take this dimension. + + // function mask. + PivotFunc nMask = PivotFunc::NONE; + if ( nOrient == sheet::DataPilotFieldOrientation_DATA ) + { + sal_Int16 eFunc = ScUnoHelpFunctions::GetShortProperty( + xDimProp, SC_UNO_DP_FUNCTION2, + sheet::GeneralFunction2::NONE ); + if ( eFunc == sheet::GeneralFunction2::AUTO ) + { + //TODO: test for numeric data + eFunc = sheet::GeneralFunction2::SUM; + } + nMask = ScDataPilotConversion::FunctionBit(eFunc); + } + else + nMask = lcl_FirstSubTotal( xDimProp ); // from first hierarchy + + // is this data layout dimension? + bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( + xDimProp, SC_UNO_DP_ISDATALAYOUT); + + // is this dimension cloned? + tools::Long nDupSource = -1; + try + { + uno::Any aOrigAny = xDimProp->getPropertyValue(SC_UNO_DP_ORIGINAL_POS); + sal_Int32 nTmp = 0; + if (aOrigAny >>= nTmp) + nDupSource = nTmp; + } + catch(uno::Exception&) + { + } + + sal_uInt8 nDupCount = 0; + if (nDupSource >= 0) + { + // this dimension is cloned. + + SCCOL nCompCol; // ID of the original dimension. + if ( bDataLayout ) + nCompCol = PIVOT_DATA_FIELD; + else + nCompCol = static_cast<SCCOL>(nDupSource); //TODO: seek source column from name + + ScPivotFieldVector::iterator it = std::find_if(aFields.begin(), aFields.end(), FindByColumn(nCompCol, nMask)); + if (it != aFields.end()) + nDupCount = it->mnDupCount + 1; + } + + aFields.emplace_back(); + ScPivotField& rField = aFields.back(); + if (bDataLayout) + { + rField.nCol = PIVOT_DATA_FIELD; + bDataFound = true; + } + else + { + rField.mnOriginalDim = nDupSource; + rField.nCol = static_cast<SCCOL>(nDim); //TODO: seek source column from name + } + + rField.nFuncMask = nMask; + rField.mnDupCount = nDupCount; + tools::Long nPos = ScUnoHelpFunctions::GetLongProperty( + xDimProp, SC_UNO_DP_POSITION); + aPos.push_back(nPos); + + try + { + if (nOrient == sheet::DataPilotFieldOrientation_DATA) + xDimProp->getPropertyValue(SC_UNO_DP_REFVALUE) + >>= rField.maFieldRef; + } + catch (uno::Exception&) + { + } + } + } + + // sort by getPosition() value + + size_t nOutCount = aFields.size(); + if (nOutCount >= 1) + { + for (size_t i = 0; i < nOutCount - 1; ++i) + { + for (size_t j = 0; j + i < nOutCount - 1; ++j) + { + if ( aPos[j+1] < aPos[j] ) + { + std::swap( aPos[j], aPos[j+1] ); + std::swap( aFields[j], aFields[j+1] ); + } + } + } + } + + if (bAddData && !bDataFound) + aFields.emplace_back(PIVOT_DATA_FIELD); + + rFields.swap(aFields); +} + +void ScDPObject::FillOldParam(ScPivotParam& rParam) const +{ + const_cast<ScDPObject*>(this)->CreateObjects(); // xSource is needed for field numbers + + if (!xSource.is()) + return; + + rParam.nCol = aOutRange.aStart.Col(); + rParam.nRow = aOutRange.aStart.Row(); + rParam.nTab = aOutRange.aStart.Tab(); + // ppLabelArr / nLabels is not changed + + bool bAddData = ( lcl_GetDataGetOrientation( xSource ) == sheet::DataPilotFieldOrientation_HIDDEN ); + lcl_FillOldFields( + rParam.maPageFields, xSource, sheet::DataPilotFieldOrientation_PAGE, false); + lcl_FillOldFields( + rParam.maColFields, xSource, sheet::DataPilotFieldOrientation_COLUMN, bAddData); + lcl_FillOldFields( + rParam.maRowFields, xSource, sheet::DataPilotFieldOrientation_ROW, false); + lcl_FillOldFields( + rParam.maDataFields, xSource, sheet::DataPilotFieldOrientation_DATA, false); + + uno::Reference<beans::XPropertySet> xProp( xSource, uno::UNO_QUERY ); + if (!xProp.is()) + return; + + try + { + rParam.bMakeTotalCol = ScUnoHelpFunctions::GetBoolProperty( xProp, + SC_UNO_DP_COLGRAND, true ); + rParam.bMakeTotalRow = ScUnoHelpFunctions::GetBoolProperty( xProp, + SC_UNO_DP_ROWGRAND, true ); + + // following properties may be missing for external sources + rParam.bIgnoreEmptyRows = ScUnoHelpFunctions::GetBoolProperty( xProp, + SC_UNO_DP_IGNOREEMPTY ); + rParam.bDetectCategories = ScUnoHelpFunctions::GetBoolProperty( xProp, + SC_UNO_DP_REPEATEMPTY ); + } + catch(uno::Exception&) + { + // no error + } +} + +static void lcl_FillLabelData( ScDPLabelData& rData, const uno::Reference< beans::XPropertySet >& xDimProp ) +{ + uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDimProp, uno::UNO_QUERY ); + if (!xDimProp.is() || !xDimSupp.is()) + return; + + uno::Reference<container::XIndexAccess> xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty( + xDimProp, SC_UNO_DP_USEDHIERARCHY); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + rData.mnUsedHier = nHierarchy; + + uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if (!xHierSupp.is()) + return; + + uno::Reference<container::XIndexAccess> xLevels = + new ScNameToIndexAccess( xHierSupp->getLevels() ); + + uno::Reference<beans::XPropertySet> xLevProp(xLevels->getByIndex(0), uno::UNO_QUERY); + if (!xLevProp.is()) + return; + + rData.mbShowAll = ScUnoHelpFunctions::GetBoolProperty( + xLevProp, SC_UNO_DP_SHOWEMPTY); + + rData.mbRepeatItemLabels = ScUnoHelpFunctions::GetBoolProperty( + xLevProp, SC_UNO_DP_REPEATITEMLABELS); + + try + { + xLevProp->getPropertyValue( SC_UNO_DP_SORTING ) + >>= rData.maSortInfo; + xLevProp->getPropertyValue( SC_UNO_DP_LAYOUT ) + >>= rData.maLayoutInfo; + xLevProp->getPropertyValue( SC_UNO_DP_AUTOSHOW ) + >>= rData.maShowInfo; + } + catch(uno::Exception&) + { + } +} + +void ScDPObject::FillLabelDataForDimension( + const uno::Reference<container::XIndexAccess>& xDims, sal_Int32 nDim, ScDPLabelData& rLabelData) +{ + uno::Reference<uno::XInterface> xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference<container::XNamed> xDimName( xIntDim, uno::UNO_QUERY ); + uno::Reference<beans::XPropertySet> xDimProp( xIntDim, uno::UNO_QUERY ); + + if (!xDimName.is() || !xDimProp.is()) + return; + + bool bData = ScUnoHelpFunctions::GetBoolProperty( + xDimProp, SC_UNO_DP_ISDATALAYOUT); + //TODO: error checking -- is "IsDataLayoutDimension" property required?? + + sal_Int32 nOrigPos = -1; + OUString aFieldName; + try + { + aFieldName = xDimName->getName(); + uno::Any aOrigAny = xDimProp->getPropertyValue(SC_UNO_DP_ORIGINAL_POS); + aOrigAny >>= nOrigPos; + } + catch(uno::Exception&) + { + } + + OUString aLayoutName = ScUnoHelpFunctions::GetStringProperty( + xDimProp, SC_UNO_DP_LAYOUTNAME, OUString()); + + OUString aSubtotalName = ScUnoHelpFunctions::GetStringProperty( + xDimProp, SC_UNO_DP_FIELD_SUBTOTALNAME, OUString()); + + // Name from the UNO dimension object may have trailing '*'s in which + // case it's a duplicate dimension. Convert that to a duplicate index. + + sal_uInt8 nDupCount = ScDPUtil::getDuplicateIndex(aFieldName); + aFieldName = ScDPUtil::getSourceDimensionName(aFieldName); + + rLabelData.maName = aFieldName; + rLabelData.mnCol = static_cast<SCCOL>(nDim); + rLabelData.mnDupCount = nDupCount; + rLabelData.mbDataLayout = bData; + rLabelData.mbIsValue = true; //TODO: check + + if (bData) + return; + + rLabelData.mnOriginalDim = static_cast<tools::Long>(nOrigPos); + rLabelData.maLayoutName = aLayoutName; + rLabelData.maSubtotalName = aSubtotalName; + if (nOrigPos >= 0) + // This is a duplicated dimension. Use the original dimension index. + nDim = nOrigPos; + GetHierarchies(nDim, rLabelData.maHiers); + GetMembers(nDim, GetUsedHierarchy(nDim), rLabelData.maMembers); + lcl_FillLabelData(rLabelData, xDimProp); + rLabelData.mnFlags = ScUnoHelpFunctions::GetLongProperty( + xDimProp, SC_UNO_DP_FLAGS ); +} + +void ScDPObject::FillLabelData(sal_Int32 nDim, ScDPLabelData& rLabels) +{ + CreateObjects(); + if (!xSource.is()) + return; + + uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions(); + uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName ); + sal_Int32 nDimCount = xDims->getCount(); + if (nDimCount <= 0 || nDim >= nDimCount) + return; + + FillLabelDataForDimension(xDims, nDim, rLabels); +} + +void ScDPObject::FillLabelData(ScPivotParam& rParam) +{ + rParam.maLabelArray.clear(); + + CreateObjects(); + if (!xSource.is()) + return; + + uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions(); + uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName ); + sal_Int32 nDimCount = xDims->getCount(); + if (nDimCount <= 0) + return; + + for (sal_Int32 nDim = 0; nDim < nDimCount; ++nDim) + { + ScDPLabelData* pNewLabel = new ScDPLabelData; + FillLabelDataForDimension(xDims, nDim, *pNewLabel); + rParam.maLabelArray.push_back(std::unique_ptr<ScDPLabelData>(pNewLabel)); + } +} + +bool ScDPObject::GetHierarchiesNA( sal_Int32 nDim, uno::Reference< container::XNameAccess >& xHiers ) +{ + bool bRet = false; + uno::Reference<container::XNameAccess> xDimsName( GetSource()->getDimensions() ); + uno::Reference<container::XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName )); + if( xIntDims.is() ) + { + uno::Reference<sheet::XHierarchiesSupplier> xHierSup(xIntDims->getByIndex( nDim ), uno::UNO_QUERY); + if (xHierSup.is()) + { + xHiers.set( xHierSup->getHierarchies() ); + bRet = xHiers.is(); + } + } + return bRet; +} + +void ScDPObject::GetHierarchies( sal_Int32 nDim, uno::Sequence< OUString >& rHiers ) +{ + uno::Reference< container::XNameAccess > xHiersNA; + if( GetHierarchiesNA( nDim, xHiersNA ) ) + { + rHiers = xHiersNA->getElementNames(); + } +} + +sal_Int32 ScDPObject::GetUsedHierarchy( sal_Int32 nDim ) +{ + sal_Int32 nHier = 0; + uno::Reference<container::XNameAccess> xDimsName( GetSource()->getDimensions() ); + uno::Reference<container::XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName )); + uno::Reference<beans::XPropertySet> xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY); + if (xDim.is()) + nHier = ScUnoHelpFunctions::GetLongProperty( xDim, SC_UNO_DP_USEDHIERARCHY ); + return nHier; +} + +bool ScDPObject::GetMembersNA( sal_Int32 nDim, uno::Reference< sheet::XMembersAccess >& xMembers ) +{ + return GetMembersNA( nDim, GetUsedHierarchy( nDim ), xMembers ); +} + +bool ScDPObject::GetMembersNA( sal_Int32 nDim, sal_Int32 nHier, uno::Reference< sheet::XMembersAccess >& xMembers ) +{ + bool bRet = false; + uno::Reference<container::XNameAccess> xDimsName( GetSource()->getDimensions() ); + uno::Reference<container::XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName )); + uno::Reference<beans::XPropertySet> xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY); + if (xDim.is()) + { + uno::Reference<sheet::XHierarchiesSupplier> xHierSup(xDim, uno::UNO_QUERY); + if (xHierSup.is()) + { + uno::Reference<container::XIndexAccess> xHiers(new ScNameToIndexAccess(xHierSup->getHierarchies())); + uno::Reference<sheet::XLevelsSupplier> xLevSupp( xHiers->getByIndex(nHier), uno::UNO_QUERY ); + if ( xLevSupp.is() ) + { + uno::Reference<container::XIndexAccess> xLevels(new ScNameToIndexAccess( xLevSupp->getLevels())); + if (xLevels.is()) + { + sal_Int32 nLevCount = xLevels->getCount(); + if (nLevCount > 0) + { + uno::Reference<sheet::XMembersSupplier> xMembSupp( xLevels->getByIndex(0), uno::UNO_QUERY ); + if ( xMembSupp.is() ) + { + xMembers.set(xMembSupp->getMembers()); + bRet = true; + } + } + } + } + } + } + return bRet; +} + +// convert old pivot tables into new datapilot tables + +namespace { + +OUString lcl_GetDimName( const uno::Reference<sheet::XDimensionsSupplier>& xSource, tools::Long nDim ) +{ + OUString aName; + if ( xSource.is() ) + { + uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions(); + uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nDimCount = xDims->getCount(); + if ( nDim < nDimCount ) + { + uno::Reference<container::XNamed> xDimName(xDims->getByIndex(nDim), uno::UNO_QUERY); + if (xDimName.is()) + { + try + { + aName = xDimName->getName(); + } + catch(uno::Exception&) + { + } + } + } + } + return aName; +} + +bool hasFieldColumn(const vector<ScPivotField>* pRefFields, SCCOL nCol) +{ + if (!pRefFields) + return false; + + return std::any_of(pRefFields->begin(), pRefFields->end(), + [&nCol](const ScPivotField& rField) { + // This array of fields contains the specified column. + return rField.nCol == nCol; }); +} + +class FindByOriginalDim +{ + tools::Long mnDim; +public: + explicit FindByOriginalDim(tools::Long nDim) : mnDim(nDim) {} + bool operator() (const ScPivotField& r) const + { + return mnDim == r.getOriginalDim(); + } +}; + +} + +void ScDPObject::ConvertOrientation( + ScDPSaveData& rSaveData, const ScPivotFieldVector& rFields, sheet::DataPilotFieldOrientation nOrient, + const Reference<XDimensionsSupplier>& xSource, + const ScDPLabelDataVector& rLabels, + const ScPivotFieldVector* pRefColFields, + const ScPivotFieldVector* pRefRowFields, + const ScPivotFieldVector* pRefPageFields ) +{ + ScPivotFieldVector::const_iterator itr, itrBeg = rFields.begin(), itrEnd = rFields.end(); + for (itr = itrBeg; itr != itrEnd; ++itr) + { + const ScPivotField& rField = *itr; + + tools::Long nCol = rField.getOriginalDim(); + PivotFunc nFuncs = rField.nFuncMask; + const sheet::DataPilotFieldReference& rFieldRef = rField.maFieldRef; + + ScDPSaveDimension* pDim = nullptr; + if ( nCol == PIVOT_DATA_FIELD ) + pDim = rSaveData.GetDataLayoutDimension(); + else + { + OUString aDocStr = lcl_GetDimName( xSource, nCol ); // cols must start at 0 + if (!aDocStr.isEmpty()) + pDim = rSaveData.GetDimensionByName(aDocStr); + else + pDim = nullptr; + } + + if (!pDim) + continue; + + if ( nOrient == sheet::DataPilotFieldOrientation_DATA ) // set summary function + { + // generate an individual entry for each function + bool bFirst = true; + + // if a dimension is used for column/row/page and data, + // use duplicated dimensions for all data occurrences + if (hasFieldColumn(pRefColFields, nCol)) + bFirst = false; + + if (bFirst && hasFieldColumn(pRefRowFields, nCol)) + bFirst = false; + + if (bFirst && hasFieldColumn(pRefPageFields, nCol)) + bFirst = false; + + if (bFirst) + { + // if set via api, a data column may occur several times + // (if the function hasn't been changed yet) -> also look for duplicate data column + bFirst = std::none_of(itrBeg, itr, FindByOriginalDim(nCol)); + } + + ScGeneralFunction eFunc = ScDataPilotConversion::FirstFunc(rField.nFuncMask); + if (!bFirst) + pDim = rSaveData.DuplicateDimension(pDim->GetName()); + pDim->SetOrientation(nOrient); + pDim->SetFunction(eFunc); + + if( rFieldRef.ReferenceType == sheet::DataPilotFieldReferenceType::NONE ) + pDim->SetReferenceValue(nullptr); + else + pDim->SetReferenceValue(&rFieldRef); + } + else // set SubTotals + { + pDim->SetOrientation( nOrient ); + + std::vector<ScGeneralFunction> nSubTotalFuncs; + nSubTotalFuncs.reserve(16); + sal_uInt16 nMask = 1; + for (sal_uInt16 nBit=0; nBit<16; nBit++) + { + if ( nFuncs & static_cast<PivotFunc>(nMask) ) + nSubTotalFuncs.push_back( ScDataPilotConversion::FirstFunc( static_cast<PivotFunc>(nMask) ) ); + nMask *= 2; + } + pDim->SetSubTotals( std::move(nSubTotalFuncs) ); + + // ShowEmpty was implicit in old tables, + // must be set for data layout dimension (not accessible in dialog) + if ( nCol == PIVOT_DATA_FIELD ) + pDim->SetShowEmpty( true ); + } + + size_t nDimIndex = rField.nCol; + pDim->RemoveLayoutName(); + pDim->RemoveSubtotalName(); + if (nDimIndex < rLabels.size()) + { + const ScDPLabelData& rLabel = *rLabels[nDimIndex]; + if (!rLabel.maLayoutName.isEmpty()) + pDim->SetLayoutName(rLabel.maLayoutName); + if (!rLabel.maSubtotalName.isEmpty()) + pDim->SetSubtotalName(rLabel.maSubtotalName); + } + } +} + +bool ScDPObject::IsOrientationAllowed( sheet::DataPilotFieldOrientation nOrient, sal_Int32 nDimFlags ) +{ + bool bAllowed = true; + switch (nOrient) + { + case sheet::DataPilotFieldOrientation_PAGE: + bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_PAGE_ORIENTATION ) == 0; + break; + case sheet::DataPilotFieldOrientation_COLUMN: + bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_COLUMN_ORIENTATION ) == 0; + break; + case sheet::DataPilotFieldOrientation_ROW: + bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_ROW_ORIENTATION ) == 0; + break; + case sheet::DataPilotFieldOrientation_DATA: + bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_DATA_ORIENTATION ) == 0; + break; + default: + { + // allowed to remove from previous orientation + } + } + return bAllowed; +} + +bool ScDPObject::HasRegisteredSources() +{ + bool bFound = false; + + uno::Reference<lang::XMultiServiceFactory> xManager = comphelper::getProcessServiceFactory(); + uno::Reference<container::XContentEnumerationAccess> xEnAc( xManager, uno::UNO_QUERY ); + if ( xEnAc.is() ) + { + uno::Reference<container::XEnumeration> xEnum = xEnAc->createContentEnumeration( + SCDPSOURCE_SERVICE ); + if ( xEnum.is() && xEnum->hasMoreElements() ) + bFound = true; + } + + return bFound; +} + +std::vector<OUString> ScDPObject::GetRegisteredSources() +{ + std::vector<OUString> aVec; + + // use implementation names... + + uno::Reference<lang::XMultiServiceFactory> xManager = comphelper::getProcessServiceFactory(); + uno::Reference<container::XContentEnumerationAccess> xEnAc( xManager, uno::UNO_QUERY ); + if ( xEnAc.is() ) + { + uno::Reference<container::XEnumeration> xEnum = xEnAc->createContentEnumeration( + SCDPSOURCE_SERVICE ); + if ( xEnum.is() ) + { + while ( xEnum->hasMoreElements() ) + { + uno::Any aAddInAny = xEnum->nextElement(); +// if ( aAddInAny.getReflection()->getTypeClass() == TypeClass_INTERFACE ) + { + uno::Reference<uno::XInterface> xIntFac; + aAddInAny >>= xIntFac; + if ( xIntFac.is() ) + { + uno::Reference<lang::XServiceInfo> xInfo( xIntFac, uno::UNO_QUERY ); + if ( xInfo.is() ) + { + OUString sName = xInfo->getImplementationName(); + aVec.push_back( sName ); + } + } + } + } + } + } + + return aVec; +} + +uno::Reference<sheet::XDimensionsSupplier> ScDPObject::CreateSource( const ScDPServiceDesc& rDesc ) +{ + OUString aImplName = rDesc.aServiceName; + uno::Reference<sheet::XDimensionsSupplier> xRet; + + uno::Reference<lang::XMultiServiceFactory> xManager = comphelper::getProcessServiceFactory(); + uno::Reference<container::XContentEnumerationAccess> xEnAc(xManager, uno::UNO_QUERY); + if (!xEnAc.is()) + return xRet; + + uno::Reference<container::XEnumeration> xEnum = + xEnAc->createContentEnumeration(SCDPSOURCE_SERVICE); + if (!xEnum.is()) + return xRet; + + while (xEnum->hasMoreElements() && !xRet.is()) + { + uno::Any aAddInAny = xEnum->nextElement(); + uno::Reference<uno::XInterface> xIntFac; + aAddInAny >>= xIntFac; + if (!xIntFac.is()) + continue; + + uno::Reference<lang::XServiceInfo> xInfo(xIntFac, uno::UNO_QUERY); + if (!xInfo.is() || xInfo->getImplementationName() != aImplName) + continue; + + try + { + // #i113160# try XSingleComponentFactory in addition to (old) XSingleServiceFactory, + // passing the context to the component (see ScUnoAddInCollection::Initialize) + + uno::Reference<uno::XInterface> xInterface; + uno::Reference<uno::XComponentContext> xCtx( + comphelper::getComponentContext(xManager)); + uno::Reference<lang::XSingleComponentFactory> xCFac( xIntFac, uno::UNO_QUERY ); + if (xCFac.is()) + xInterface = xCFac->createInstanceWithContext(xCtx); + + if (!xInterface.is()) + { + uno::Reference<lang::XSingleServiceFactory> xFac( xIntFac, uno::UNO_QUERY ); + if ( xFac.is() ) + xInterface = xFac->createInstance(); + } + + uno::Reference<lang::XInitialization> xInit( xInterface, uno::UNO_QUERY ); + if (xInit.is()) + { + // initialize + uno::Sequence<uno::Any> aSeq(4); + uno::Any* pArray = aSeq.getArray(); + pArray[0] <<= rDesc.aParSource; + pArray[1] <<= rDesc.aParName; + pArray[2] <<= rDesc.aParUser; + pArray[3] <<= rDesc.aParPass; + xInit->initialize( aSeq ); + } + xRet.set( xInterface, uno::UNO_QUERY ); + } + catch(uno::Exception&) + { + } + } + + return xRet; +} + +#if DUMP_PIVOT_TABLE + +void ScDPObject::Dump() const +{ + if (pSaveData) + pSaveData->Dump(); + + if (mpTableData) + mpTableData->Dump(); +} + +void ScDPObject::DumpCache() const +{ + if (!mpTableData) + return; + + const ScDPCache &rCache = mpTableData->GetCacheTable().getCache(); + + rCache.Dump(); +} +#endif + +ScDPCollection::SheetCaches::SheetCaches(ScDocument& rDoc) : mrDoc(rDoc) {} + +namespace { + +struct FindInvalidRange +{ + bool operator() (const ScRange& r) const + { + return !r.IsValid(); + } +}; + +void setGroupItemsToCache( ScDPCache& rCache, const o3tl::sorted_vector<ScDPObject*>& rRefs ) +{ + // Go through all referencing pivot tables, and re-fill the group dimension info. + for (const ScDPObject* pObj : rRefs) + { + const ScDPSaveData* pSave = pObj->GetSaveData(); + if (!pSave) + continue; + + const ScDPDimensionSaveData* pGroupDims = pSave->GetExistingDimensionData(); + if (!pGroupDims) + continue; + + pGroupDims->WriteToCache(rCache); + } +} + +} + +bool ScDPCollection::SheetCaches::hasCache(const ScRange& rRange) const +{ + RangeIndexType::const_iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it == maRanges.end()) + return false; + + // Already cached. + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::const_iterator const itCache = m_Caches.find(nIndex); + return itCache != m_Caches.end(); +} + +const ScDPCache* ScDPCollection::SheetCaches::getCache(const ScRange& rRange, const ScDPDimensionSaveData* pDimData) +{ + RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it != maRanges.end()) + { + // Already cached. + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::iterator const itCache = m_Caches.find(nIndex); + if (itCache == m_Caches.end()) + { + OSL_FAIL("Cache pool and index pool out-of-sync !!!"); + return nullptr; + } + + if (pDimData) + { + (itCache->second)->ClearGroupFields(); + pDimData->WriteToCache(*itCache->second); + } + + return itCache->second.get(); + } + + // Not cached. Create a new cache. + ::std::unique_ptr<ScDPCache> pCache(new ScDPCache(mrDoc)); + pCache->InitFromDoc(mrDoc, rRange); + if (pDimData) + pDimData->WriteToCache(*pCache); + + // Get the smallest available range index. + it = std::find_if(maRanges.begin(), maRanges.end(), FindInvalidRange()); + + size_t nIndex = maRanges.size(); + if (it == maRanges.end()) + { + // All range indices are valid. Append a new index. + maRanges.push_back(rRange); + } + else + { + // Slot with invalid range. Re-use this slot. + *it = rRange; + nIndex = std::distance(maRanges.begin(), it); + } + + const ScDPCache* p = pCache.get(); + m_Caches.insert(std::make_pair(nIndex, std::move(pCache))); + return p; +} + +ScDPCache* ScDPCollection::SheetCaches::getExistingCache(const ScRange& rRange) +{ + RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it == maRanges.end()) + // Not cached. + return nullptr; + + // Already cached. + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::iterator const itCache = m_Caches.find(nIndex); + if (itCache == m_Caches.end()) + { + OSL_FAIL("Cache pool and index pool out-of-sync !!!"); + return nullptr; + } + + return itCache->second.get(); +} + +const ScDPCache* ScDPCollection::SheetCaches::getExistingCache(const ScRange& rRange) const +{ + RangeIndexType::const_iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it == maRanges.end()) + // Not cached. + return nullptr; + + // Already cached. + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::const_iterator const itCache = m_Caches.find(nIndex); + if (itCache == m_Caches.end()) + { + OSL_FAIL("Cache pool and index pool out-of-sync !!!"); + return nullptr; + } + + return itCache->second.get(); +} + +size_t ScDPCollection::SheetCaches::size() const +{ + return m_Caches.size(); +} + +void ScDPCollection::SheetCaches::updateReference( + UpdateRefMode eMode, const ScRange& r, SCCOL nDx, SCROW nDy, SCTAB nDz) +{ + if (maRanges.empty()) + // No caches. + return; + + for (ScRange& rKeyRange : maRanges) + { + SCCOL nCol1 = rKeyRange.aStart.Col(); + SCROW nRow1 = rKeyRange.aStart.Row(); + SCTAB nTab1 = rKeyRange.aStart.Tab(); + SCCOL nCol2 = rKeyRange.aEnd.Col(); + SCROW nRow2 = rKeyRange.aEnd.Row(); + SCTAB nTab2 = rKeyRange.aEnd.Tab(); + + ScRefUpdateRes eRes = ScRefUpdate::Update( + &mrDoc, eMode, + r.aStart.Col(), r.aStart.Row(), r.aStart.Tab(), + r.aEnd.Col(), r.aEnd.Row(), r.aEnd.Tab(), nDx, nDy, nDz, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + + if (eRes != UR_NOTHING) + { + // range updated. + ScRange aNew(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + rKeyRange = aNew; + } + } +} + +void ScDPCollection::SheetCaches::updateCache(const ScRange& rRange, o3tl::sorted_vector<ScDPObject*>& rRefs) +{ + RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it == maRanges.end()) + { + // Not cached. Nothing to do. + rRefs.clear(); + return; + } + + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::iterator const itCache = m_Caches.find(nIndex); + if (itCache == m_Caches.end()) + { + OSL_FAIL("Cache pool and index pool out-of-sync !!!"); + rRefs.clear(); + return; + } + + ScDPCache& rCache = *itCache->second; + + // Update the cache with new cell values. This will clear all group dimension info. + rCache.InitFromDoc(mrDoc, rRange); + + o3tl::sorted_vector<ScDPObject*> aRefs(rCache.GetAllReferences()); + rRefs.swap(aRefs); + + // Make sure to re-populate the group dimension info. + setGroupItemsToCache(rCache, rRefs); +} + +bool ScDPCollection::SheetCaches::remove(const ScDPCache* p) +{ + CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(), + [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; }); + if (it != m_Caches.end()) + { + size_t idx = it->first; + m_Caches.erase(it); + maRanges[idx].SetInvalid(); + return true; + } + return false; +} + +const std::vector<ScRange>& ScDPCollection::SheetCaches::getAllRanges() const +{ + return maRanges; +} + +ScDPCollection::NameCaches::NameCaches(ScDocument& rDoc) : mrDoc(rDoc) {} + +bool ScDPCollection::NameCaches::hasCache(const OUString& rName) const +{ + return m_Caches.count(rName) != 0; +} + +const ScDPCache* ScDPCollection::NameCaches::getCache( + const OUString& rName, const ScRange& rRange, const ScDPDimensionSaveData* pDimData) +{ + CachesType::const_iterator const itr = m_Caches.find(rName); + if (itr != m_Caches.end()) + // already cached. + return itr->second.get(); + + ::std::unique_ptr<ScDPCache> pCache(new ScDPCache(mrDoc)); + pCache->InitFromDoc(mrDoc, rRange); + if (pDimData) + pDimData->WriteToCache(*pCache); + + const ScDPCache *const p = pCache.get(); + m_Caches.insert(std::make_pair(rName, std::move(pCache))); + return p; +} + +ScDPCache* ScDPCollection::NameCaches::getExistingCache(const OUString& rName) +{ + CachesType::iterator const itr = m_Caches.find(rName); + return itr != m_Caches.end() ? itr->second.get() : nullptr; +} + +size_t ScDPCollection::NameCaches::size() const +{ + return m_Caches.size(); +} + +void ScDPCollection::NameCaches::updateCache( + const OUString& rName, const ScRange& rRange, o3tl::sorted_vector<ScDPObject*>& rRefs) +{ + CachesType::iterator const itr = m_Caches.find(rName); + if (itr == m_Caches.end()) + { + rRefs.clear(); + return; + } + + ScDPCache& rCache = *itr->second; + // Update the cache with new cell values. This will clear all group dimension info. + rCache.InitFromDoc(mrDoc, rRange); + + o3tl::sorted_vector<ScDPObject*> aRefs(rCache.GetAllReferences()); + rRefs.swap(aRefs); + + // Make sure to re-populate the group dimension info. + setGroupItemsToCache(rCache, rRefs); +} + +bool ScDPCollection::NameCaches::remove(const ScDPCache* p) +{ + CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(), + [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; }); + if (it != m_Caches.end()) + { + m_Caches.erase(it); + return true; + } + return false; +} + +ScDPCollection::DBType::DBType(sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) : + mnSdbType(nSdbType), maDBName(rDBName), maCommand(rCommand) {} + +bool ScDPCollection::DBType::less::operator() (const DBType& left, const DBType& right) const +{ + return left < right; +} + +ScDPCollection::DBCaches::DBCaches(ScDocument& rDoc) : mrDoc(rDoc) {} + +bool ScDPCollection::DBCaches::hasCache(sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) const +{ + DBType aType(nSdbType, rDBName, rCommand); + CachesType::const_iterator const itr = m_Caches.find(aType); + return itr != m_Caches.end(); +} + +const ScDPCache* ScDPCollection::DBCaches::getCache( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand, + const ScDPDimensionSaveData* pDimData) +{ + DBType aType(nSdbType, rDBName, rCommand); + CachesType::const_iterator const itr = m_Caches.find(aType); + if (itr != m_Caches.end()) + // already cached. + return itr->second.get(); + + uno::Reference<sdbc::XRowSet> xRowSet = createRowSet(nSdbType, rDBName, rCommand); + if (!xRowSet.is()) + return nullptr; + + ::std::unique_ptr<ScDPCache> pCache(new ScDPCache(mrDoc)); + SvNumberFormatter aFormat( comphelper::getProcessComponentContext(), ScGlobal::eLnge); + DBConnector aDB(*pCache, xRowSet, aFormat.GetNullDate()); + if (!aDB.isValid()) + return nullptr; + + if (!pCache->InitFromDataBase(aDB)) + { + // initialization failed. + comphelper::disposeComponent(xRowSet); + return nullptr; + } + + if (pDimData) + pDimData->WriteToCache(*pCache); + + ::comphelper::disposeComponent(xRowSet); + const ScDPCache* p = pCache.get(); + m_Caches.insert(std::make_pair(aType, std::move(pCache))); + return p; +} + +ScDPCache* ScDPCollection::DBCaches::getExistingCache( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) +{ + DBType aType(nSdbType, rDBName, rCommand); + CachesType::iterator const itr = m_Caches.find(aType); + return itr != m_Caches.end() ? itr->second.get() : nullptr; +} + +uno::Reference<sdbc::XRowSet> ScDPCollection::DBCaches::createRowSet( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) +{ + uno::Reference<sdbc::XRowSet> xRowSet; + try + { + xRowSet.set(comphelper::getProcessServiceFactory()->createInstance( + SC_SERVICE_ROWSET), + UNO_QUERY); + + uno::Reference<beans::XPropertySet> xRowProp(xRowSet, UNO_QUERY); + OSL_ENSURE( xRowProp.is(), "can't get RowSet" ); + if (!xRowProp.is()) + { + xRowSet.set(nullptr); + return xRowSet; + } + + // set source parameters + + xRowProp->setPropertyValue( SC_DBPROP_DATASOURCENAME, Any(rDBName) ); + xRowProp->setPropertyValue( SC_DBPROP_COMMAND, Any(rCommand) ); + xRowProp->setPropertyValue( SC_DBPROP_COMMANDTYPE, Any(nSdbType) ); + + uno::Reference<sdb::XCompletedExecution> xExecute( xRowSet, uno::UNO_QUERY ); + if ( xExecute.is() ) + { + uno::Reference<task::XInteractionHandler> xHandler( + task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr), + uno::UNO_QUERY_THROW); + xExecute->executeWithCompletion( xHandler ); + } + else + xRowSet->execute(); + + return xRowSet; + } + catch ( const sdbc::SQLException& rError ) + { + //! store error message + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + rError.Message)); + xInfoBox->run(); + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "Unexpected exception in database"); + } + + xRowSet.set(nullptr); + return xRowSet; +} + +void ScDPCollection::DBCaches::updateCache( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand, + o3tl::sorted_vector<ScDPObject*>& rRefs) +{ + DBType aType(nSdbType, rDBName, rCommand); + CachesType::iterator const it = m_Caches.find(aType); + if (it == m_Caches.end()) + { + // not cached. + rRefs.clear(); + return; + } + + ScDPCache& rCache = *it->second; + + uno::Reference<sdbc::XRowSet> xRowSet = createRowSet(nSdbType, rDBName, rCommand); + if (!xRowSet.is()) + { + rRefs.clear(); + return; + } + + SvNumberFormatter aFormat( comphelper::getProcessComponentContext(), ScGlobal::eLnge); + DBConnector aDB(rCache, xRowSet, aFormat.GetNullDate()); + if (!aDB.isValid()) + return; + + if (!rCache.InitFromDataBase(aDB)) + { + // initialization failed. + rRefs.clear(); + comphelper::disposeComponent(xRowSet); + return; + } + + comphelper::disposeComponent(xRowSet); + o3tl::sorted_vector<ScDPObject*> aRefs(rCache.GetAllReferences()); + aRefs.swap(rRefs); + + // Make sure to re-populate the group dimension info. + setGroupItemsToCache(rCache, rRefs); +} + +bool ScDPCollection::DBCaches::remove(const ScDPCache* p) +{ + CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(), + [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; }); + if (it != m_Caches.end()) + { + m_Caches.erase(it); + return true; + } + return false; +} + +ScDPCollection::ScDPCollection(ScDocument& rDocument) : + mrDoc(rDocument), + maSheetCaches(rDocument), + maNameCaches(rDocument), + maDBCaches(rDocument) +{ +} + +ScDPCollection::ScDPCollection(const ScDPCollection& r) : + mrDoc(r.mrDoc), + maSheetCaches(r.mrDoc), + maNameCaches(r.mrDoc), + maDBCaches(r.mrDoc) +{ +} + +ScDPCollection::~ScDPCollection() +{ + maTables.clear(); +} + +namespace { + +/** + * Unary predicate to match DP objects by the table ID. + */ +class MatchByTable +{ + SCTAB mnTab; +public: + explicit MatchByTable(SCTAB nTab) : mnTab(nTab) {} + + bool operator() (const std::unique_ptr<ScDPObject>& rObj) const + { + return rObj->GetOutRange().aStart.Tab() == mnTab; + } +}; + +} + +TranslateId ScDPCollection::ReloadCache(const ScDPObject* pDPObj, o3tl::sorted_vector<ScDPObject*>& rRefs) +{ + if (!pDPObj) + return STR_ERR_DATAPILOTSOURCE; + + if (pDPObj->IsSheetData()) + { + // data source is internal sheet. + const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc(); + if (!pDesc) + return STR_ERR_DATAPILOTSOURCE; + + TranslateId pErrId = pDesc->CheckSourceRange(); + if (pErrId) + return pErrId; + + if (pDesc->HasRangeName()) + { + // cache by named range + ScDPCollection::NameCaches& rCaches = GetNameCaches(); + if (rCaches.hasCache(pDesc->GetRangeName())) + rCaches.updateCache(pDesc->GetRangeName(), pDesc->GetSourceRange(), rRefs); + else + { + // Not cached yet. Collect all tables that use this named + // range as data source. + GetAllTables(pDesc->GetRangeName(), rRefs); + } + } + else + { + // cache by cell range + ScDPCollection::SheetCaches& rCaches = GetSheetCaches(); + if (rCaches.hasCache(pDesc->GetSourceRange())) + rCaches.updateCache(pDesc->GetSourceRange(), rRefs); + else + { + // Not cached yet. Collect all tables that use this range as + // data source. + GetAllTables(pDesc->GetSourceRange(), rRefs); + } + } + } + else if (pDPObj->IsImportData()) + { + // data source is external database. + const ScImportSourceDesc* pDesc = pDPObj->GetImportSourceDesc(); + if (!pDesc) + return STR_ERR_DATAPILOTSOURCE; + + ScDPCollection::DBCaches& rCaches = GetDBCaches(); + if (rCaches.hasCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject)) + rCaches.updateCache( + pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs); + else + { + // Not cached yet. Collect all tables that use this range as + // data source. + GetAllTables(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs); + } + } + return {}; +} + +bool ScDPCollection::ReloadGroupsInCache(const ScDPObject* pDPObj, o3tl::sorted_vector<ScDPObject*>& rRefs) +{ + if (!pDPObj) + return false; + + const ScDPSaveData* pSaveData = pDPObj->GetSaveData(); + if (!pSaveData) + return false; + + // Note: Unlike reloading cache, when modifying the group dimensions the + // cache may not have all its references when this method is called. + // Therefore, we need to always call GetAllTables to get its correct + // references even when the cache exists. This may become a non-issue + // if/when we implement loading and saving of pivot caches. + + ScDPCache* pCache = nullptr; + + if (pDPObj->IsSheetData()) + { + // data source is internal sheet. + const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc(); + if (!pDesc) + return false; + + if (pDesc->HasRangeName()) + { + // cache by named range + ScDPCollection::NameCaches& rCaches = GetNameCaches(); + if (rCaches.hasCache(pDesc->GetRangeName())) + pCache = rCaches.getExistingCache(pDesc->GetRangeName()); + else + { + // Not cached yet. Cache the source dimensions. Groups will + // be added below. + pCache = const_cast<ScDPCache*>( + rCaches.getCache(pDesc->GetRangeName(), pDesc->GetSourceRange(), nullptr)); + } + GetAllTables(pDesc->GetRangeName(), rRefs); + } + else + { + // cache by cell range + ScDPCollection::SheetCaches& rCaches = GetSheetCaches(); + if (rCaches.hasCache(pDesc->GetSourceRange())) + pCache = rCaches.getExistingCache(pDesc->GetSourceRange()); + else + { + // Not cached yet. Cache the source dimensions. Groups will + // be added below. + pCache = const_cast<ScDPCache*>( + rCaches.getCache(pDesc->GetSourceRange(), nullptr)); + } + GetAllTables(pDesc->GetSourceRange(), rRefs); + } + } + else if (pDPObj->IsImportData()) + { + // data source is external database. + const ScImportSourceDesc* pDesc = pDPObj->GetImportSourceDesc(); + if (!pDesc) + return false; + + ScDPCollection::DBCaches& rCaches = GetDBCaches(); + if (rCaches.hasCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject)) + pCache = rCaches.getExistingCache( + pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject); + else + { + // Not cached yet. Cache the source dimensions. Groups will + // be added below. + pCache = const_cast<ScDPCache*>( + rCaches.getCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, nullptr)); + } + GetAllTables(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs); + } + + if (!pCache) + return false; + + // Clear the existing group/field data from the cache, and rebuild it from the + // dimension data. + pCache->ClearAllFields(); + const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData(); + if (pDimData) + pDimData->WriteToCache(*pCache); + return true; +} + +bool ScDPCollection::GetReferenceGroups(const ScDPObject& rDPObj, const ScDPDimensionSaveData** pGroups) const +{ + for (const std::unique_ptr<ScDPObject>& aTable : maTables) + { + const ScDPObject& rRefObj = *aTable; + + if (&rRefObj == &rDPObj) + continue; + + if (rDPObj.IsSheetData()){ + if(!rRefObj.IsSheetData()) + continue; + + const ScSheetSourceDesc* pDesc = rDPObj.GetSheetDesc(); + const ScSheetSourceDesc* pRefDesc = rRefObj.GetSheetDesc(); + if (pDesc == nullptr || pRefDesc == nullptr) + continue; + + if (pDesc->HasRangeName()) + { + if (!pRefDesc->HasRangeName()) + continue; + + if (pDesc->GetRangeName() == pRefDesc->GetRangeName()) + { + *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData(); + return true; + } + } + else + { + if (pRefDesc->HasRangeName()) + continue; + + if (pDesc->GetSourceRange() == pRefDesc->GetSourceRange()) + { + *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData(); + return true; + } + } + } + else if (rDPObj.IsImportData()) + { + if (!rRefObj.IsImportData ()) + continue; + + const ScImportSourceDesc* pDesc = rDPObj.GetImportSourceDesc(); + const ScImportSourceDesc* pRefDesc = rRefObj.GetImportSourceDesc(); + if (pDesc == nullptr || pRefDesc == nullptr) + continue; + + if (pDesc->aDBName == pRefDesc->aDBName && + pDesc->aObject == pRefDesc->aObject && + pDesc->GetCommandType() == pRefDesc->GetCommandType()) + { + *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData(); + return true; + } + + } + } + return false; +} + + +void ScDPCollection::DeleteOnTab( SCTAB nTab ) +{ + maTables.erase( std::remove_if(maTables.begin(), maTables.end(), MatchByTable(nTab)), maTables.end()); +} + +void ScDPCollection::UpdateReference( UpdateRefMode eUpdateRefMode, + const ScRange& r, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + for (auto& rxTable : maTables) + rxTable->UpdateReference(eUpdateRefMode, r, nDx, nDy, nDz); + + // Update the source ranges of the caches. + maSheetCaches.updateReference(eUpdateRefMode, r, nDx, nDy, nDz); +} + +void ScDPCollection::CopyToTab( SCTAB nOld, SCTAB nNew ) +{ + TablesType aAdded; + for (const auto& rxTable : maTables) + { + const ScDPObject& rObj = *rxTable; + ScRange aOutRange = rObj.GetOutRange(); + if (aOutRange.aStart.Tab() != nOld) + continue; + + ScAddress& s = aOutRange.aStart; + ScAddress& e = aOutRange.aEnd; + s.SetTab(nNew); + e.SetTab(nNew); + ScDPObject* pNew = new ScDPObject(rObj); + pNew->SetOutRange(aOutRange); + mrDoc.ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable); + aAdded.push_back(std::unique_ptr<ScDPObject>(pNew)); + } + + std::move(aAdded.begin(), aAdded.end(), std::back_inserter(maTables)); +} + +bool ScDPCollection::RefsEqual( const ScDPCollection& r ) const +{ + return std::equal(maTables.begin(), maTables.end(), r.maTables.begin(), r.maTables.end(), + [](const TablesType::value_type& a, const TablesType::value_type& b) { return a->RefsEqual(*b); }); +} + +void ScDPCollection::WriteRefsTo( ScDPCollection& r ) const +{ + if ( maTables.size() == r.maTables.size() ) + { + //TODO: assert equal names? + TablesType::iterator itr2 = r.maTables.begin(); + for (const auto& rxTable : maTables) + { + rxTable->WriteRefsTo(**itr2); + ++itr2; + } + } + else + { + // #i8180# If data pilot tables were deleted with their sheet, + // this collection contains extra entries that must be restored. + // Matching objects are found by their names. + size_t nSrcSize = maTables.size(); + size_t nDestSize = r.maTables.size(); + OSL_ENSURE( nSrcSize >= nDestSize, "WriteRefsTo: missing entries in document" ); + for (size_t nSrcPos = 0; nSrcPos < nSrcSize; ++nSrcPos) + { + const ScDPObject& rSrcObj = *maTables[nSrcPos]; + const OUString& aName = rSrcObj.GetName(); + bool bFound = false; + for (size_t nDestPos = 0; nDestPos < nDestSize && !bFound; ++nDestPos) + { + ScDPObject& rDestObj = *r.maTables[nDestPos]; + if (rDestObj.GetName() == aName) + { + rSrcObj.WriteRefsTo(rDestObj); // found object, copy refs + bFound = true; + } + } + + if (!bFound) + { + // none found, re-insert deleted object (see ScUndoDataPilot::Undo) + r.InsertNewTable(std::make_unique<ScDPObject>(rSrcObj)); + } + } + OSL_ENSURE( maTables.size() == r.maTables.size(), "WriteRefsTo: couldn't restore all entries" ); + } +} + +size_t ScDPCollection::GetCount() const +{ + return maTables.size(); +} + +ScDPObject& ScDPCollection::operator [](size_t nIndex) +{ + return *maTables[nIndex]; +} + +const ScDPObject& ScDPCollection::operator [](size_t nIndex) const +{ + return *maTables[nIndex]; +} + +ScDPObject* ScDPCollection::GetByName(std::u16string_view rName) const +{ + for (std::unique_ptr<ScDPObject> const & pObject : maTables) + { + if (pObject->GetName() == rName) + return pObject.get(); + } + + return nullptr; +} + +OUString ScDPCollection::CreateNewName() const +{ + size_t n = maTables.size(); + for (size_t nAdd = 0; nAdd <= n; ++nAdd) // nCount+1 tries + { + OUString aNewName = "DataPilot" + OUString::number(1 + nAdd); + if (std::none_of(maTables.begin(), maTables.end(), + [&aNewName](const TablesType::value_type& rxObj) { return rxObj->GetName() == aNewName; })) + return aNewName; // found unused Name + } + return OUString(); // should not happen +} + +void ScDPCollection::FreeTable(const ScDPObject* pDPObject) +{ + const ScRange& rOutRange = pDPObject->GetOutRange(); + const ScAddress& s = rOutRange.aStart; + const ScAddress& e = rOutRange.aEnd; + mrDoc.RemoveFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable); + + auto funcRemoveCondition = [pDPObject] (std::unique_ptr<ScDPObject> const & pCurrent) + { + return pCurrent.get() == pDPObject; + }; + + maTables.erase(std::remove_if(maTables.begin(), maTables.end(), funcRemoveCondition), maTables.end()); +} + +ScDPObject* ScDPCollection::InsertNewTable(std::unique_ptr<ScDPObject> pDPObj) +{ + const ScRange& rOutRange = pDPObj->GetOutRange(); + const ScAddress& s = rOutRange.aStart; + const ScAddress& e = rOutRange.aEnd; + mrDoc.ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable); + + maTables.push_back(std::move(pDPObj)); + return maTables.back().get(); +} + +bool ScDPCollection::HasTable(const ScDPObject* pDPObj) const +{ + for (const std::unique_ptr<ScDPObject>& aTable : maTables) + { + if (aTable.get() == pDPObj) + { + return true; + } + } + return false; +} + +ScDPCollection::SheetCaches& ScDPCollection::GetSheetCaches() +{ + return maSheetCaches; +} + +const ScDPCollection::SheetCaches& ScDPCollection::GetSheetCaches() const +{ + return maSheetCaches; +} + +ScDPCollection::NameCaches& ScDPCollection::GetNameCaches() +{ + return maNameCaches; +} + +const ScDPCollection::NameCaches& ScDPCollection::GetNameCaches() const +{ + return maNameCaches; +} + +ScDPCollection::DBCaches& ScDPCollection::GetDBCaches() +{ + return maDBCaches; +} + +const ScDPCollection::DBCaches& ScDPCollection::GetDBCaches() const +{ + return maDBCaches; +} + +ScRangeList ScDPCollection::GetAllTableRanges( SCTAB nTab ) const +{ + return std::for_each(maTables.begin(), maTables.end(), AccumulateOutputRanges(nTab)).getRanges(); +} + +bool ScDPCollection::IntersectsTableByColumns( SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab ) const +{ + return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTableByColumns(nCol1, nCol2, nRow, nTab)); +} + +bool ScDPCollection::IntersectsTableByRows( SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab ) const +{ + return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTableByRows(nCol, nRow1, nRow2, nTab)); +} + +bool ScDPCollection::HasTable( const ScRange& rRange ) const +{ + return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTable(rRange)); +} + +#if DEBUG_PIVOT_TABLE + +namespace { + +struct DumpTable +{ + void operator() (const std::unique_ptr<ScDPObject>& rObj) const + { + cout << "-- '" << rObj->GetName() << "'" << endl; + ScDPSaveData* pSaveData = rObj->GetSaveData(); + if (!pSaveData) + return; + + pSaveData->Dump(); + + cout << endl; // blank line + } +}; + +} + +void ScDPCollection::DumpTables() const +{ + std::for_each(maTables.begin(), maTables.end(), DumpTable()); +} + +#endif + +void ScDPCollection::RemoveCache(const ScDPCache* pCache) +{ + if (maSheetCaches.remove(pCache)) + // sheet cache removed. + return; + + if (maNameCaches.remove(pCache)) + // named range cache removed. + return; + + if (maDBCaches.remove(pCache)) + // database cache removed. + return; +} + +void ScDPCollection::GetAllTables(const ScRange& rSrcRange, o3tl::sorted_vector<ScDPObject*>& rRefs) const +{ + o3tl::sorted_vector<ScDPObject*> aRefs; + for (const auto& rxTable : maTables) + { + const ScDPObject& rObj = *rxTable; + if (!rObj.IsSheetData()) + // Source is not a sheet range. + continue; + + const ScSheetSourceDesc* pDesc = rObj.GetSheetDesc(); + if (!pDesc) + continue; + + if (pDesc->HasRangeName()) + // This table has a range name as its source. + continue; + + if (pDesc->GetSourceRange() != rSrcRange) + // Different source range. + continue; + + aRefs.insert(const_cast<ScDPObject*>(&rObj)); + } + + rRefs.swap(aRefs); +} + +void ScDPCollection::GetAllTables(std::u16string_view rSrcName, o3tl::sorted_vector<ScDPObject*>& rRefs) const +{ + o3tl::sorted_vector<ScDPObject*> aRefs; + for (const auto& rxTable : maTables) + { + const ScDPObject& rObj = *rxTable; + if (!rObj.IsSheetData()) + // Source is not a sheet range. + continue; + + const ScSheetSourceDesc* pDesc = rObj.GetSheetDesc(); + if (!pDesc) + continue; + + if (!pDesc->HasRangeName()) + // This table probably has a sheet range as its source. + continue; + + if (pDesc->GetRangeName() != rSrcName) + // Different source name. + continue; + + aRefs.insert(const_cast<ScDPObject*>(&rObj)); + } + + rRefs.swap(aRefs); +} + +void ScDPCollection::GetAllTables( + sal_Int32 nSdbType, std::u16string_view rDBName, std::u16string_view rCommand, + o3tl::sorted_vector<ScDPObject*>& rRefs) const +{ + o3tl::sorted_vector<ScDPObject*> aRefs; + for (const auto& rxTable : maTables) + { + const ScDPObject& rObj = *rxTable; + if (!rObj.IsImportData()) + // Source data is not a database. + continue; + + const ScImportSourceDesc* pDesc = rObj.GetImportSourceDesc(); + if (!pDesc) + continue; + + if (pDesc->aDBName != rDBName || pDesc->aObject != rCommand || pDesc->GetCommandType() != nSdbType) + // Different database source. + continue; + + aRefs.insert(const_cast<ScDPObject*>(&rObj)); + } + + rRefs.swap(aRefs); +} + +bool operator<(const ScDPCollection::DBType& left, const ScDPCollection::DBType& right) +{ + if (left.mnSdbType != right.mnSdbType) + return left.mnSdbType < right.mnSdbType; + + if (left.maDBName != right.maDBName) + return left.maDBName < right.maDBName; + + return left.maCommand < right.maCommand; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |