/* -*- 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/. */ #include #include #include #include #include #include #include #include namespace sc { namespace { class NameResolver { private: ScDPTableData& mrTableData; ScDPCache const& mrCache; std::unordered_map> maNameCache; void fillNamesForDimension(std::vector& rNames, sal_Int32 nDimension) { for (const auto& rItemData : mrCache.GetDimMemberValues(nDimension)) { OUString sFormattedName; if (rItemData.HasStringData() || rItemData.IsEmpty()) sFormattedName = rItemData.GetString(); else sFormattedName = ScDPObject::GetFormattedString(&mrTableData, nDimension, rItemData.GetValue()); rNames.push_back(sFormattedName); } } public: NameResolver(ScDPTableData& rTableData, ScDPCache const& rCache) : mrTableData(rTableData) , mrCache(rCache) { } OUString getNameForIndex(sal_uInt32 nIndex, sal_Int32 nDimension) { auto iterator = maNameCache.find(nDimension); if (iterator == maNameCache.end()) { std::vector aNames; fillNamesForDimension(aNames, nDimension); iterator = maNameCache.emplace(nDimension, aNames).first; } const std::vector& rNames = iterator->second; if (nIndex >= rNames.size()) return OUString(); return rNames[nIndex]; } }; void initLines(std::vector& rLines, std::vector const& rFields) { for (size_t i = 0; i < rFields.size(); i++) { size_t nFieldLength(rFields[i].maResult.getLength()); if (rLines.size() < nFieldLength) rLines.resize(nFieldLength); for (LineData& rLineData : rLines) { rLineData.maFields.resize(rFields.size()); } } } Selection const* findSelection(PivotTableFormat const& rFormat, tools::Long nDimension) { for (Selection const& rSelection : rFormat.getSelections()) { if (rSelection.nField == nDimension) return &rSelection; } return nullptr; } void fillOutputFieldFromSelection(FormatOutputField& rOutputField, Selection const& rSelection, size_t nSelectionIndex, NameResolver& rNameResolver) { if (rSelection.nIndices.empty()) { rOutputField.bMatchesAll = true; } else { if (rSelection.nIndices.size() > 1 && rSelection.nIndices.size() > nSelectionIndex) rOutputField.nIndex = rSelection.nIndices[nSelectionIndex]; else rOutputField.nIndex = rSelection.nIndices[0]; if (rOutputField.nDimension == -2) rOutputField.aName = "DATA"; else rOutputField.aName = rNameResolver.getNameForIndex(rOutputField.nIndex, rOutputField.nDimension); } rOutputField.bSet = true; } void initFormatOutputField(size_t nSelectionIndex, std::vector& rOutputFields, std::vector const& rFields, PivotTableFormat const& rFormat, NameResolver& rNameResolver) { rOutputFields.resize(rFields.size()); for (size_t i = 0; i < rOutputFields.size(); i++) { FormatOutputField& rOutputField = rOutputFields[i]; if (!rFields[i].mbDataLayout) rOutputField.nDimension = rFields[i].mnDim; Selection const* pSelection = findSelection(rFormat, rOutputField.nDimension); if (pSelection == nullptr) continue; fillOutputFieldFromSelection(rOutputField, *pSelection, nSelectionIndex, rNameResolver); } } } // end anonymous namespace void FormatOutput::prepare(SCTAB nTab, std::vector const& rColumnFields, std::vector const& rRowFields, bool bColumnFieldIsDataOnly) { if (!mpFormats) return; // initialize row and column lines, so the number of fields matches the pivot table output initLines(maRowLines, rRowFields); if (rColumnFields.size() == 0 && bColumnFieldIsDataOnly) { maColumnLines.resize(1); maColumnLines[0].maFields.resize(1); } else { initLines(maColumnLines, rColumnFields); } // check the table data exists auto* pTableData = mrObject.GetTableData(); if (!pTableData) return; ScDPFilteredCache const& rFilteredCache = pTableData->GetCacheTable(); ScDPCache const& rCache = rFilteredCache.getCache(); NameResolver aNameResolver(*pTableData, rCache); // Initialize format output entries (FormatOutputEntry) and set the data already available from output fields // (rColumnFields and rRowFields) and the pivot table format list (PivotTableFormat). for (PivotTableFormat const& rFormat : mpFormats->getVector()) { size_t nMaxNumberOfIndices = 1; for (auto const& rSelection : rFormat.aSelections) { if (rSelection.nIndices.size() > 1) nMaxNumberOfIndices = rSelection.nIndices.size(); } for (size_t nSelectionIndex = 0; nSelectionIndex < nMaxNumberOfIndices; nSelectionIndex++) { sc::FormatOutputEntry aEntry; aEntry.pPattern = rFormat.pPattern; aEntry.onTab = nTab; aEntry.eType = rFormat.eType; initFormatOutputField(nSelectionIndex, aEntry.aRowOutputFields, rRowFields, rFormat, aNameResolver); // If column fields list is empty, but there is a data field in columns that is not part of column fields if (rColumnFields.size() == 0 && bColumnFieldIsDataOnly) { // Initialize column output fields to have 1 data output field aEntry.aColumnOutputFields.resize(1); FormatOutputField& rOutputField = aEntry.aColumnOutputFields[0]; rOutputField.nDimension = -2; Selection const* pSelection = findSelection(rFormat, -2); if (pSelection) fillOutputFieldFromSelection(rOutputField, *pSelection, nSelectionIndex, aNameResolver); } else { initFormatOutputField(nSelectionIndex, aEntry.aColumnOutputFields, rColumnFields, rFormat, aNameResolver); } maFormatOutputEntries.push_back(aEntry); } } } void FormatOutput::insertEmptyDataColumn(SCCOL nColPos, SCROW nRowPos) { if (!mpFormats) return; LineData& rLine = maColumnLines[0]; rLine.oLine = nColPos; rLine.oPosition = nRowPos; FieldData& rFieldData = rLine.maFields[0]; rFieldData.nIndex = 0; rFieldData.bIsSet = true; } namespace { void fillLineAndFieldData(std::vector& rLineDataVector, size_t nFieldIndex, ScDPOutLevelData const& rField, tools::Long nMemberIndex, sheet::MemberResult const& rMember, SCCOLROW nLine, SCCOLROW nPosition) { LineData& rLine = rLineDataVector[nMemberIndex]; rLine.oLine = nLine; rLine.oPosition = nPosition; FieldData& rFieldData = rLine.maFields[nFieldIndex]; if (!rField.mbDataLayout) rFieldData.mnDimension = rField.mnDim; rFieldData.aName = rMember.Name; rFieldData.nIndex = nMemberIndex; rFieldData.bIsSet = true; rFieldData.bIsMember = rMember.Flags & sheet::MemberResultFlags::HASMEMBER; rFieldData.bSubtotal = rMember.Flags & sheet::MemberResultFlags::SUBTOTAL; rFieldData.bContinue = rMember.Flags & sheet::MemberResultFlags::CONTINUE; // Search previous entries for the name / value if (rFieldData.bContinue) { tools::Long nCurrent = nMemberIndex - 1; while (nCurrent >= 0 && rLineDataVector[nCurrent].maFields[nFieldIndex].bContinue) nCurrent--; if (nCurrent >= 0) { FieldData& rCurrentFieldData = rLineDataVector[nCurrent].maFields[nFieldIndex]; rFieldData.aName = rCurrentFieldData.aName; rFieldData.nIndex = rCurrentFieldData.nIndex; } } } } // end anonymous namespace void FormatOutput::insertFieldMember(size_t nFieldIndex, ScDPOutLevelData const& rField, tools::Long nMemberIndex, sheet::MemberResult const& rMember, SCCOL nColPos, SCROW nRowPos, sc::FormatResultDirection eResultDirection) { if (!mpFormats) return; if (eResultDirection == sc::FormatResultDirection::ROW) fillLineAndFieldData(maRowLines, nFieldIndex, rField, nMemberIndex, rMember, nRowPos, nColPos); else if (eResultDirection == sc::FormatResultDirection::COLUMN) fillLineAndFieldData(maColumnLines, nFieldIndex, rField, nMemberIndex, rMember, nColPos, nRowPos); } namespace { void checkForMatchingLines(std::vector const& rLines, std::vector const& rFormatOutputField, FormatType eType, std::vector>& rMatches, std::vector>& rMaybeMatches) { for (LineData const& rLineData : rLines) { size_t nMatch = 0; size_t nMaybeMatch = 0; size_t nNoOfFields = rLineData.maFields.size(); for (size_t nIndex = 0; nIndex < nNoOfFields; nIndex++) { FieldData const& rFieldData = rLineData.maFields[nIndex]; FormatOutputField const& rFormatEntry = rFormatOutputField[nIndex]; bool bFieldMatch = false; bool bFieldMaybeMatch = false; tools::Long nDimension = rFieldData.mnDimension; if (nDimension == rFormatEntry.nDimension) { if (rFormatEntry.bSet) { if (rFormatEntry.bMatchesAll && !rFieldData.bSubtotal) bFieldMatch = true; else if (nDimension == -2 && rFieldData.nIndex == rFormatEntry.nIndex) bFieldMatch = true; else if (nDimension != -2 && rFieldData.aName == rFormatEntry.aName) bFieldMatch = true; } else if (!rFormatEntry.bSet && eType == FormatType::Data && !rFieldData.bIsMember && !rFieldData.bContinue) { bFieldMatch = true; } else { bFieldMaybeMatch = true; } } if (!bFieldMatch && !bFieldMaybeMatch) break; if (bFieldMatch) nMatch++; if (bFieldMaybeMatch) nMaybeMatch++; } if (nMatch == nNoOfFields) { rMatches.push_back(std::cref(rLineData)); } else if (nMatch + nMaybeMatch == nNoOfFields) { rMaybeMatches.push_back(std::cref(rLineData)); } } } /** Check the lines in matches and maybe matches and output */ void evaluateMatches(ScDocument& rDocument, std::vector> const& rMatches, std::vector> const& rMaybeMatches, std::vector& aRows, std::vector& aColumns, FormatOutputEntry const& rOutputEntry, FormatResultDirection eResultDirection) { // We expect that tab and pattern to be set or this method shouldn't be called at all assert(rOutputEntry.onTab); assert(rOutputEntry.pPattern); if (rMatches.empty() && rMaybeMatches.empty()) return; bool bMaybeExists = rMatches.empty(); auto const& rLineDataVector = bMaybeExists ? rMaybeMatches : rMatches; for (LineData const& rLineData : rLineDataVector) { // Can't continue if we don't have complete row/column data if (!rLineData.oLine || !rLineData.oPosition) continue; if (rOutputEntry.eType == FormatType::Label && !bMaybeExists) { // Primary axis is set to column (line) then row (position) SCCOLROW nColumn = *rLineData.oLine; SCCOLROW nRow = *rLineData.oPosition; // In row orientation, the primary axis is row, then column, so we need to swap if (eResultDirection == FormatResultDirection::ROW) std::swap(nRow, nColumn); // Set the pattern to the sheet rDocument.ApplyPattern(nColumn, nRow, *rOutputEntry.onTab, *rOutputEntry.pPattern); } else if (rOutputEntry.eType == FormatType::Data) { if (eResultDirection == FormatResultDirection::ROW) aRows.push_back(*rLineData.oLine); else if (eResultDirection == FormatResultDirection::COLUMN) aColumns.push_back(*rLineData.oLine); } } } } // end anonymous namespace void FormatOutput::apply(ScDocument& rDocument) { if (!mpFormats) return; for (auto const& rOutputEntry : maFormatOutputEntries) { if (!rOutputEntry.onTab || !rOutputEntry.pPattern) continue; std::vector aRows; std::vector aColumns; { std::vector> rMatches; std::vector> rMaybeMatches; checkForMatchingLines(maRowLines, rOutputEntry.aRowOutputFields, rOutputEntry.eType, rMatches, rMaybeMatches); evaluateMatches(rDocument, rMatches, rMaybeMatches, aRows, aColumns, rOutputEntry, FormatResultDirection::ROW); } { std::vector> rMatches; std::vector> rMaybeMatches; checkForMatchingLines(maColumnLines, rOutputEntry.aColumnOutputFields, rOutputEntry.eType, rMatches, rMaybeMatches); evaluateMatches(rDocument, rMatches, rMaybeMatches, aRows, aColumns, rOutputEntry, FormatResultDirection::COLUMN); } if (!aColumns.empty() && !aRows.empty() && rOutputEntry.eType == FormatType::Data) { for (SCCOLROW nRow : aRows) for (SCCOLROW nColumn : aColumns) rDocument.ApplyPattern(nColumn, nRow, *rOutputEntry.onTab, *rOutputEntry.pPattern); } } } } // end sc /* vim:set shiftwidth=4 softtabstop=4 expandtab: */