/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 mxRowSet; uno::Reference mxRow; uno::Reference mxMetaData; Date maNullDate; public: DBConnector(ScDPCache& rCache, const uno::Reference& 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& xRowSet, const Date& rNullDate) : mrCache(rCache), mxRowSet(xRowSet), maNullDate(rNullDate) { Reference 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(::tools::Time::hourPerDay) + aTime.Minutes / static_cast(::tools::Time::minutePerDay) + aTime.Seconds / static_cast(::tools::Time::secondPerDay) + aTime.NanoSeconds / static_cast(::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(::tools::Time::hourPerDay) + aStamp.Minutes / static_cast(::tools::Time::minutePerDay) + aStamp.Seconds / static_cast(::tools::Time::secondPerDay) + aStamp.NanoSeconds / static_cast(::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& xSource ) { sheet::DataPilotFieldOrientation nRet = sheet::DataPilotFieldOrientation_HIDDEN; if ( xSource.is() ) { uno::Reference xDimNameAccess = xSource->getDimensions(); const uno::Sequence aDimNames = xDimNameAccess->getElementNames(); for (const OUString& rDimName : aDimNames) { uno::Reference 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 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& 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& 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& 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& 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 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(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(pDoc, *pSheetDesc, *pCache); } } } // grouping (for cell or database data) if (pData && pDimData) { auto pGroupData = std::make_shared(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 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(mpTableData.get()); if (pData) { // Replace the existing group table data with the source data. mpTableData = pData->GetSourceTableData(); } return; } ScDPGroupTableData* pData = dynamic_cast(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& pSource = pData->GetSourceTableData(); auto pGroupData = std::make_shared(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(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& rNames ) { vector 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& rMembers ) { Reference< sheet::XMembersAccess > xMembersNA; if (!GetMembersNA( nDim, nHier, xMembersNA )) return false; Reference xMembersIA( new ScNameToIndexAccess(xMembersNA) ); sal_Int32 nCount = xMembersIA->getCount(); vector aMembers; aMembers.reserve(nCount); for (sal_Int32 i = 0; i < nCount; ++i) { Reference xMember; try { xMember = Reference(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 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( aParam.nCol1 + nDiffX ); aParam.nCol2 = sal::static_int_cast( aParam.nCol2 + nDiffX ); aParam.nRow1 += nDiffY; //TODO: used? aParam.nRow2 += nDiffY; //TODO: used? SCSIZE nEC = aParam.GetEntryCount(); for (SCSIZE i=0; iSetQueryParam(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& rFilters) { CreateOutput(); vector aFilters; if (!pOutput->GetDataResultPositionData(aFilters, rPos)) return false; sal_Int32 n = static_cast(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 >& rTableData) { CreateOutput(); Reference xDrillDownData(xSource, UNO_QUERY); if (!xDrillDownData.is()) return; Sequence filters; if (!GetDataFieldPositionData(rPos, filters)) return; rTableData = xDrillDownData->getDrillDownData(filters); } bool ScDPObject::IsDimNameInUse(std::u16string_view rName) const { if (!xSource.is()) return false; Reference xDims = xSource->getDimensions(); const Sequence aDimNames = xDims->getElementNames(); for (const OUString& rDimName : aDimNames) { if (rDimName.equalsIgnoreAsciiCase(rName)) return true; Reference 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 xDimsName = xSource->getDimensions(); uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); tools::Long nDimCount = xDims->getCount(); if ( nDim < nDimCount ) { uno::Reference xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY); uno::Reference xDimName( xIntDim, uno::UNO_QUERY ); uno::Reference 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 xDimsName = xSource->getDimensions(); uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); tools::Long nDimCount = xDims->getCount(); if ( nDim < nDimCount ) { uno::Reference xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY); if ( xDimProp.is() ) { try { uno::Any aOrigAny = xDimProp->getPropertyValue( SC_UNO_DP_ORIGINAL ); uno::Reference 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 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 & 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& rFilters) { if (!mbEnableGetPivotData) return std::numeric_limits::quiet_NaN(); CreateObjects(); std::vector aDataDims; pSaveData->GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_DATA, aDataDims); if (aDataDims.empty()) return std::numeric_limits::quiet_NaN(); std::vector::iterator it = std::find_if( aDataDims.begin(), aDataDims.end(), FindByName(ScGlobal::getCharClass().uppercase(rDataFieldName))); if (it == aDataDims.end()) return std::numeric_limits::quiet_NaN(); size_t nDataIndex = std::distance(aDataDims.begin(), it); uno::Reference xDPResults(xSource, uno::UNO_QUERY); if (!xDPResults.is()) return std::numeric_limits::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 aFilters(n); auto aFiltersRange = asNonConstRange(aFilters); for (size_t i = 0; i < n; ++i) aFiltersRange[i] = rFilters[i]; uno::Sequence aRes = xDPResults->getFilteredResults(aFilters); if (nDataIndex >= o3tl::make_unsigned(aRes.getLength())) return std::numeric_limits::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= 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(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(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& rFilters, std::vector& rFilterFuncs, std::u16string_view rFilterList ) { // parse the string rFilterList into parameters for GetPivotData CreateObjects(); // create xSource if not already done std::vector aDataNames; // data fields (source name) std::vector aGivenNames; // data fields (compound name) std::vector aFieldNames; // column/row/data fields std::vector< uno::Sequence > aFieldValueNames; std::vector< uno::Sequence > aFieldValues; // get all the field and item names uno::Reference xDimsName = xSource->getDimensions(); uno::Reference xIntDims = new ScNameToIndexAccess( xDimsName ); sal_Int32 nDimCount = xIntDims->getCount(); for ( sal_Int32 nDim = 0; nDim xIntDim(xIntDims->getByIndex(nDim), uno::UNO_QUERY); uno::Reference xDim( xIntDim, uno::UNO_QUERY ); uno::Reference xDimProp( xDim, uno::UNO_QUERY ); uno::Reference 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 xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); sal_Int32 nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp, SC_UNO_DP_USEDHIERARCHY ); if ( nHierarchy >= xHiers->getCount() ) nHierarchy = 0; uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), uno::UNO_QUERY); if ( xHierSupp.is() ) { uno::Reference xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); sal_Int32 nLevCount = xLevels->getCount(); for (sal_Int32 nLev=0; nLev xLevel(xLevels->getByIndex(nLev), uno::UNO_QUERY); uno::Reference xLevNam( xLevel, uno::UNO_QUERY ); uno::Reference xLevSupp( xLevel, uno::UNO_QUERY ); if ( xLevNam.is() && xLevSupp.is() ) { uno::Reference xMembers = xLevSupp->getMembers(); OUString aFieldName( xLevNam->getName() ); // getElementNames() and getLocaleIndependentElementNames() // must be consecutive calls to obtain strings in matching order. uno::Sequence aMemberValueNames( xMembers->getElementNames() ); uno::Sequence 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; nDataPosGetCacheTable().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& rItemNames = aFieldValueNames[nField]; const uno::Sequence& 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 xDim; uno::Reference xDimsName = xSource->getDimensions(); uno::Reference 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 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 xHiers; uno::Reference xHierSupp( xDim, uno::UNO_QUERY ); if ( xHierSupp.is() ) { uno::Reference xHiersName = xHierSupp->getHierarchies(); xHiers = new ScNameToIndexAccess( xHiersName ); nHierCount = xHiers->getCount(); } uno::Reference 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 xLevels; uno::Reference xLevSupp( xHier, uno::UNO_QUERY ); if ( xLevSupp.is() ) { uno::Reference xLevsName = xLevSupp->getLevels(); xLevels = new ScNameToIndexAccess( xLevsName ); nLevCount = xLevels->getCount(); } uno::Reference 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 xMembers; uno::Reference 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 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& xDimProp ) // PIVOT_FUNC mask { uno::Reference xDimSupp( xDimProp, uno::UNO_QUERY ); if ( xDimProp.is() && xDimSupp.is() ) { uno::Reference xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp, SC_UNO_DP_USEDHIERARCHY ); if ( nHierarchy >= xHiers->getCount() ) nHierarchy = 0; uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), uno::UNO_QUERY); if ( xHierSupp.is() ) { uno::Reference xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); uno::Reference xLevel(xLevels->getByIndex(0), uno::UNO_QUERY); uno::Reference xLevProp( xLevel, uno::UNO_QUERY ); if ( xLevProp.is() ) { uno::Any aSubAny; try { aSubAny = xLevProp->getPropertyValue( SC_UNO_DP_SUBTOTAL2 ); } catch(uno::Exception&) { } uno::Sequence 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& 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 aPos; uno::Reference xDimsName = xSource->getDimensions(); uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); tools::Long nDimCount = xDims->getCount(); for (tools::Long nDim = 0; nDim < nDimCount; ++nDim) { // dimension properties uno::Reference 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(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(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(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 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 xDimSupp( xDimProp, uno::UNO_QUERY ); if (!xDimProp.is() || !xDimSupp.is()) return; uno::Reference 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 xHierSupp(xHiers->getByIndex(nHierarchy), uno::UNO_QUERY); if (!xHierSupp.is()) return; uno::Reference xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); uno::Reference 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& xDims, sal_Int32 nDim, ScDPLabelData& rLabelData) { uno::Reference xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY); uno::Reference xDimName( xIntDim, uno::UNO_QUERY ); uno::Reference 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(nDim); rLabelData.mnDupCount = nDupCount; rLabelData.mbDataLayout = bData; rLabelData.mbIsValue = true; //TODO: check if (bData) return; rLabelData.mnOriginalDim = static_cast(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 xDimsName = xSource->getDimensions(); uno::Reference 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 xDimsName = xSource->getDimensions(); uno::Reference 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(pNewLabel)); } } bool ScDPObject::GetHierarchiesNA( sal_Int32 nDim, uno::Reference< container::XNameAccess >& xHiers ) { bool bRet = false; uno::Reference xDimsName( GetSource()->getDimensions() ); uno::Reference xIntDims(new ScNameToIndexAccess( xDimsName )); if( xIntDims.is() ) { uno::Reference 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 xDimsName( GetSource()->getDimensions() ); uno::Reference xIntDims(new ScNameToIndexAccess( xDimsName )); uno::Reference 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 xDimsName( GetSource()->getDimensions() ); uno::Reference xIntDims(new ScNameToIndexAccess( xDimsName )); uno::Reference xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY); if (xDim.is()) { uno::Reference xHierSup(xDim, uno::UNO_QUERY); if (xHierSup.is()) { uno::Reference xHiers(new ScNameToIndexAccess(xHierSup->getHierarchies())); uno::Reference xLevSupp( xHiers->getByIndex(nHier), uno::UNO_QUERY ); if ( xLevSupp.is() ) { uno::Reference xLevels(new ScNameToIndexAccess( xLevSupp->getLevels())); if (xLevels.is()) { sal_Int32 nLevCount = xLevels->getCount(); if (nLevCount > 0) { uno::Reference 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& xSource, tools::Long nDim ) { OUString aName; if ( xSource.is() ) { uno::Reference xDimsName = xSource->getDimensions(); uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); tools::Long nDimCount = xDims->getCount(); if ( nDim < nDimCount ) { uno::Reference xDimName(xDims->getByIndex(nDim), uno::UNO_QUERY); if (xDimName.is()) { try { aName = xDimName->getName(); } catch(uno::Exception&) { } } } } return aName; } bool hasFieldColumn(const vector* 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& 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 nSubTotalFuncs; nSubTotalFuncs.reserve(16); sal_uInt16 nMask = 1; for (sal_uInt16 nBit=0; nBit<16; nBit++) { if ( nFuncs & static_cast(nMask) ) nSubTotalFuncs.push_back( ScDataPilotConversion::FirstFunc( static_cast(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 xManager = comphelper::getProcessServiceFactory(); uno::Reference xEnAc( xManager, uno::UNO_QUERY ); if ( xEnAc.is() ) { uno::Reference xEnum = xEnAc->createContentEnumeration( SCDPSOURCE_SERVICE ); if ( xEnum.is() && xEnum->hasMoreElements() ) bFound = true; } return bFound; } std::vector ScDPObject::GetRegisteredSources() { std::vector aVec; // use implementation names... uno::Reference xManager = comphelper::getProcessServiceFactory(); uno::Reference xEnAc( xManager, uno::UNO_QUERY ); if ( xEnAc.is() ) { uno::Reference xEnum = xEnAc->createContentEnumeration( SCDPSOURCE_SERVICE ); if ( xEnum.is() ) { while ( xEnum->hasMoreElements() ) { uno::Any aAddInAny = xEnum->nextElement(); // if ( aAddInAny.getReflection()->getTypeClass() == TypeClass_INTERFACE ) { uno::Reference xIntFac; aAddInAny >>= xIntFac; if ( xIntFac.is() ) { uno::Reference xInfo( xIntFac, uno::UNO_QUERY ); if ( xInfo.is() ) { OUString sName = xInfo->getImplementationName(); aVec.push_back( sName ); } } } } } } return aVec; } uno::Reference ScDPObject::CreateSource( const ScDPServiceDesc& rDesc ) { OUString aImplName = rDesc.aServiceName; uno::Reference xRet; uno::Reference xManager = comphelper::getProcessServiceFactory(); uno::Reference xEnAc(xManager, uno::UNO_QUERY); if (!xEnAc.is()) return xRet; uno::Reference xEnum = xEnAc->createContentEnumeration(SCDPSOURCE_SERVICE); if (!xEnum.is()) return xRet; while (xEnum->hasMoreElements() && !xRet.is()) { uno::Any aAddInAny = xEnum->nextElement(); uno::Reference xIntFac; aAddInAny >>= xIntFac; if (!xIntFac.is()) continue; uno::Reference 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 xInterface; uno::Reference xCtx( comphelper::getComponentContext(xManager)); uno::Reference xCFac( xIntFac, uno::UNO_QUERY ); if (xCFac.is()) xInterface = xCFac->createInstanceWithContext(xCtx); if (!xInterface.is()) { uno::Reference xFac( xIntFac, uno::UNO_QUERY ); if ( xFac.is() ) xInterface = xFac->createInstance(); } uno::Reference xInit( xInterface, uno::UNO_QUERY ); if (xInit.is()) { // initialize uno::Sequence 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& 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 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& 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 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& 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 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& 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 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 xRowSet = createRowSet(nSdbType, rDBName, rCommand); if (!xRowSet.is()) return nullptr; ::std::unique_ptr 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 ScDPCollection::DBCaches::createRowSet( sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) { uno::Reference xRowSet; try { xRowSet.set(comphelper::getProcessServiceFactory()->createInstance( SC_SERVICE_ROWSET), UNO_QUERY); uno::Reference 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 xExecute( xRowSet, uno::UNO_QUERY ); if ( xExecute.is() ) { uno::Reference 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 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& 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 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 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& rObj) const { return rObj->GetOutRange().aStart.Tab() == mnTab; } }; } TranslateId ScDPCollection::ReloadCache(const ScDPObject* pDPObj, o3tl::sorted_vector& 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& 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( 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( 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( 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& 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(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(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 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 const & pCurrent) { return pCurrent.get() == pDPObject; }; maTables.erase(std::remove_if(maTables.begin(), maTables.end(), funcRemoveCondition), maTables.end()); } ScDPObject* ScDPCollection::InsertNewTable(std::unique_ptr 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& 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& 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& rRefs) const { o3tl::sorted_vector 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(&rObj)); } rRefs.swap(aRefs); } void ScDPCollection::GetAllTables(std::u16string_view rSrcName, o3tl::sorted_vector& rRefs) const { o3tl::sorted_vector 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(&rObj)); } rRefs.swap(aRefs); } void ScDPCollection::GetAllTables( sal_Int32 nSdbType, std::u16string_view rDBName, std::u16string_view rCommand, o3tl::sorted_vector& rRefs) const { o3tl::sorted_vector 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(&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: */