Files
loongoffice/sc/source/core/data/PivotTableFormatOutput.cxx
Tomaž Vajngerl c0f44787c0 pivot: handle whole row/column selections, added many tests
This adds support for selecting whole rows/columns, where the field
reference is set but no indices are present. This means that all
the data cells apply (selecting the whole thing), which are not
subtotals. For this the support for multiple matching and maybe
matching results was added (previously max 1 would apply), which
would apply the pattern to multiple cells in the pivot table.

In addition handle labels of columns with multiple data fields
correctly.

This also adds many more test scenarios, which all cover the
changes mentioned above.

Change-Id: Ibcdaa933c69991eaa55a2ad18b9f9a838308d478
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166384
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
2024-04-22 08:22:04 +02:00

442 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <pivot/PivotTableFormatOutput.hxx>
#include <pivot/DPOutLevelData.hxx>
#include <dpoutput.hxx>
#include <dpobject.hxx>
#include <dptabdat.hxx>
#include <dpcache.hxx>
#include <document.hxx>
#include <com/sun/star/sheet/MemberResultFlags.hpp>
namespace sc
{
namespace
{
class NameResolver
{
private:
ScDPTableData& mrTableData;
ScDPCache const& mrCache;
std::unordered_map<sal_Int32, std::vector<OUString>> maNameCache;
void fillNamesForDimension(std::vector<OUString>& 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<OUString> aNames;
fillNamesForDimension(aNames, nDimension);
iterator = maNameCache.emplace(nDimension, aNames).first;
}
const std::vector<OUString>& rNames = iterator->second;
if (nIndex >= rNames.size())
return OUString();
return rNames[nIndex];
}
};
void initLines(std::vector<LineData>& rLines, std::vector<ScDPOutLevelData> 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<FormatOutputField>& rOutputFields,
std::vector<ScDPOutLevelData> 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<ScDPOutLevelData> const& rColumnFields,
std::vector<ScDPOutLevelData> 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<LineData>& 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<LineData> const& rLines,
std::vector<FormatOutputField> const& rFormatOutputField,
FormatType eType,
std::vector<std::reference_wrapper<const LineData>>& rMatches,
std::vector<std::reference_wrapper<const LineData>>& 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<std::reference_wrapper<const LineData>> const& rMatches,
std::vector<std::reference_wrapper<const LineData>> const& rMaybeMatches,
std::vector<SCCOLROW>& aRows, std::vector<SCCOLROW>& 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<SCCOLROW> aRows;
std::vector<SCCOLROW> aColumns;
{
std::vector<std::reference_wrapper<const LineData>> rMatches;
std::vector<std::reference_wrapper<const LineData>> rMaybeMatches;
checkForMatchingLines(maRowLines, rOutputEntry.aRowOutputFields, rOutputEntry.eType,
rMatches, rMaybeMatches);
evaluateMatches(rDocument, rMatches, rMaybeMatches, aRows, aColumns, rOutputEntry,
FormatResultDirection::ROW);
}
{
std::vector<std::reference_wrapper<const LineData>> rMatches;
std::vector<std::reference_wrapper<const LineData>> 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: */