/* -*- 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: */
↑ V1007 The value from the potentially uninitialized optional 'rOutputEntry.onTab' is used. Probably it is a mistake.