316 lines
12 KiB
C++
316 lines
12 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 <grouptokenconverter.hxx>
|
|
#include <document.hxx>
|
|
#include <formulacell.hxx>
|
|
#include <tokenarray.hxx>
|
|
#include <refdata.hxx>
|
|
|
|
#include <formula/token.hxx>
|
|
#include <formula/vectortoken.hxx>
|
|
|
|
using namespace formula;
|
|
|
|
bool ScGroupTokenConverter::isSelfReferenceRelative(const ScAddress& rRefPos, SCROW nRelRow)
|
|
{
|
|
if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
|
|
return false;
|
|
|
|
SCROW nLen = mrCell.GetCellGroup()->mnLength;
|
|
SCROW nEndRow = mrPos.Row() + nLen - 1;
|
|
|
|
if (nRelRow < 0)
|
|
{
|
|
SCROW nTest = nEndRow;
|
|
nTest += nRelRow;
|
|
if (nTest >= mrPos.Row())
|
|
return true;
|
|
}
|
|
else if (nRelRow > 0)
|
|
{
|
|
SCROW nTest = mrPos.Row(); // top row.
|
|
nTest += nRelRow;
|
|
if (nTest <= nEndRow)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScGroupTokenConverter::isSelfReferenceAbsolute(const ScAddress& rRefPos)
|
|
{
|
|
if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
|
|
return false;
|
|
|
|
SCROW nLen = mrCell.GetCellGroup()->mnLength;
|
|
SCROW nEndRow = mrPos.Row() + nLen - 1;
|
|
|
|
if (rRefPos.Row() < mrPos.Row())
|
|
return false;
|
|
|
|
if (rRefPos.Row() > nEndRow)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
SCROW ScGroupTokenConverter::trimLength(SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCROW nRowLen)
|
|
{
|
|
SCROW nLastRow = nRow + nRowLen - 1; // current last row.
|
|
nLastRow = mrDoc.GetLastDataRow(nTab, nCol1, nCol2, nLastRow);
|
|
if (nLastRow < (nRow + nRowLen - 1))
|
|
{
|
|
// This can end up negative! Was that the original intent, or
|
|
// is it accidental? Was it not like that originally but the
|
|
// surrounding conditions changed?
|
|
const bool bFail = o3tl::checked_sub(nLastRow + 1, nRow, nRowLen);
|
|
// Anyway, let's assume it doesn't make sense to return a
|
|
// negative value here. But should we then return 0 or 1? In
|
|
// the "Column is empty" case below, we return 1, why!? And,
|
|
// at the callsites there are tests for a zero value returned
|
|
// from this function (but not for a negative one).
|
|
if (bFail || nRowLen < 0)
|
|
nRowLen = 0;
|
|
}
|
|
else if (nLastRow == 0)
|
|
// Column is empty.
|
|
nRowLen = 1;
|
|
|
|
return nRowLen;
|
|
}
|
|
|
|
ScGroupTokenConverter::ScGroupTokenConverter(
|
|
ScTokenArray& rGroupTokens, ScDocument& rDoc, const ScFormulaCell& rCell, const ScAddress& rPos) :
|
|
mrGroupTokens(rGroupTokens),
|
|
mrDoc(rDoc),
|
|
mrCell(rCell),
|
|
mrPos(rPos)
|
|
{
|
|
}
|
|
|
|
bool ScGroupTokenConverter::convert( const ScTokenArray& rCode, sc::FormulaLogger::GroupScope& rScope )
|
|
{
|
|
#if 0
|
|
{ // debug to start with:
|
|
ScCompiler aComp( &mrDoc, mrPos, rCode, formula::FormulaGrammar::GRAM_NATIVE_XL_R1C1);
|
|
OUStringBuffer aAsString;
|
|
aComp.CreateStringFromTokenArray(aAsString);
|
|
}
|
|
#endif
|
|
|
|
const SCROW nLen = mrCell.GetCellGroup()->mnLength;
|
|
formula::FormulaTokenArrayPlainIterator aIter(rCode);
|
|
for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
|
|
{
|
|
// A reference can be either absolute or relative. If it's absolute,
|
|
// convert it to a static value token. If relative, convert it to a
|
|
// vector reference token. Note: we only care about relative vs
|
|
// absolute reference state for row directions.
|
|
|
|
switch (p->GetType())
|
|
{
|
|
case svSingleRef:
|
|
{
|
|
ScSingleRefData aRef = *p->GetSingleRef();
|
|
if( aRef.IsDeleted())
|
|
return false;
|
|
ScAddress aRefPos = aRef.toAbs(mrDoc, mrPos);
|
|
if (aRef.IsRowRel())
|
|
{
|
|
if (isSelfReferenceRelative(aRefPos, aRef.Row()))
|
|
return false;
|
|
|
|
// Trim data array length to actual data range.
|
|
SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row(), nLen);
|
|
// Fetch double array guarantees that the length of the
|
|
// returned array equals or greater than the requested
|
|
// length.
|
|
|
|
formula::VectorRefArray aArray;
|
|
if (nTrimLen)
|
|
{
|
|
#ifdef DBG_UTIL
|
|
// All the necessary Interpret() calls for all the cells
|
|
// should have been already handled by ScDependantsCalculator
|
|
// calling HandleRefArrayForParallelism(), and that handling also checks
|
|
// for cycles etc. Recursively calling Interpret() from here (which shouldn't
|
|
// happen) could lead to unhandled problems.
|
|
// Also, because of caching FetchVectorRefArray() fetches values for all rows
|
|
// up to the maximum one, so check those too.
|
|
mrDoc.AssertNoInterpretNeeded(
|
|
ScAddress(aRefPos.Col(), 0, aRefPos.Tab()), nTrimLen + aRefPos.Row());
|
|
#endif
|
|
aArray = mrDoc.FetchVectorRefArray(aRefPos, nTrimLen);
|
|
}
|
|
|
|
if (!aArray.isValid())
|
|
return false;
|
|
|
|
formula::SingleVectorRefToken aTok(aArray, nTrimLen);
|
|
mrGroupTokens.AddToken(aTok);
|
|
rScope.addRefMessage(mrPos, aRefPos, nLen, aArray);
|
|
|
|
if (nTrimLen && !mxFormulaGroupContext)
|
|
{
|
|
//tdf#98880 if the SingleVectorRefToken relies on the
|
|
//underlying storage provided by the Document
|
|
//FormulaGroupContext, take a reference to it here to
|
|
//ensure that backing storage exists for our lifetime
|
|
mxFormulaGroupContext = mrDoc.GetFormulaGroupContext();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Absolute row reference.
|
|
if (isSelfReferenceAbsolute(aRefPos))
|
|
return false;
|
|
|
|
formula::FormulaTokenRef pNewToken = mrDoc.ResolveStaticReference(aRefPos);
|
|
if (!pNewToken)
|
|
return false;
|
|
|
|
mrGroupTokens.AddToken(*pNewToken);
|
|
rScope.addRefMessage(mrPos, aRefPos, *pNewToken);
|
|
}
|
|
}
|
|
break;
|
|
case svDoubleRef:
|
|
{
|
|
// This code may break in case of implicit intersection, leading to unnecessarily large
|
|
// matrix operations and possibly incorrect results (=C:C/D:D). That is handled by
|
|
// having ScCompiler check that there are no possible implicit intersections.
|
|
// Additionally some functions such as INDEX() and OFFSET() require a reference,
|
|
// that is handled by denylisting those opcodes in ScTokenArray::CheckToken().
|
|
|
|
ScComplexRefData aRef = *p->GetDoubleRef();
|
|
if( aRef.IsDeleted())
|
|
return false;
|
|
ScRange aAbs = aRef.toAbs(mrDoc, mrPos);
|
|
|
|
// Multiple sheets not handled by vector/matrix.
|
|
if (aAbs.aStart.Tab() != aAbs.aEnd.Tab())
|
|
return false;
|
|
|
|
// Check for self reference.
|
|
if (aRef.Ref1.IsRowRel())
|
|
{
|
|
if (isSelfReferenceRelative(aAbs.aStart, aRef.Ref1.Row()))
|
|
return false;
|
|
}
|
|
else if (isSelfReferenceAbsolute(aAbs.aStart))
|
|
return false;
|
|
|
|
if (aRef.Ref2.IsRowRel())
|
|
{
|
|
if (isSelfReferenceRelative(aAbs.aEnd, aRef.Ref2.Row()))
|
|
return false;
|
|
}
|
|
else if (isSelfReferenceAbsolute(aAbs.aEnd))
|
|
return false;
|
|
|
|
// Row reference is relative.
|
|
bool bAbsFirst = !aRef.Ref1.IsRowRel();
|
|
bool bAbsLast = !aRef.Ref2.IsRowRel();
|
|
ScAddress aRefPos = aAbs.aStart;
|
|
size_t nCols = aAbs.aEnd.Col() - aAbs.aStart.Col() + 1;
|
|
std::vector<formula::VectorRefArray> aArrays;
|
|
aArrays.reserve(nCols);
|
|
SCROW nRefRowSize = aAbs.aEnd.Row() - aAbs.aStart.Row() + 1;
|
|
SCROW nArrayLength = nRefRowSize;
|
|
if (!bAbsLast)
|
|
{
|
|
// range end position is relative. Extend the array length.
|
|
SCROW nLastRefRowOffset = aAbs.aEnd.Row() - mrPos.Row();
|
|
SCROW nLastRefRow = mrPos.Row() + nLen - 1 + nLastRefRowOffset;
|
|
SCROW nNewLength = nLastRefRow - aAbs.aStart.Row() + 1;
|
|
if (nNewLength > nArrayLength)
|
|
nArrayLength = nNewLength;
|
|
}
|
|
|
|
// Trim trailing empty rows.
|
|
SCROW nRequestedLength = nArrayLength; // keep the original length.
|
|
nArrayLength = trimLength(aRefPos.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), aRefPos.Row(), nArrayLength);
|
|
|
|
for (SCCOL i = aAbs.aStart.Col(); i <= aAbs.aEnd.Col(); ++i)
|
|
{
|
|
aRefPos.SetCol(i);
|
|
formula::VectorRefArray aArray;
|
|
if (nArrayLength)
|
|
{
|
|
#ifdef DBG_UTIL
|
|
mrDoc.AssertNoInterpretNeeded(
|
|
ScAddress(aRefPos.Col(), 0, aRefPos.Tab()), nArrayLength + aRefPos.Row());
|
|
#endif
|
|
aArray = mrDoc.FetchVectorRefArray(aRefPos, nArrayLength);
|
|
}
|
|
|
|
if (!aArray.isValid())
|
|
return false;
|
|
|
|
aArrays.push_back(aArray);
|
|
}
|
|
|
|
std::vector<formula::VectorRefArray> aArraysTmp = aArrays;
|
|
formula::DoubleVectorRefToken aTok( std::move(aArraysTmp), nArrayLength, nRefRowSize, bAbsFirst, bAbsLast );
|
|
mrGroupTokens.AddToken(aTok);
|
|
rScope.addRefMessage(mrPos, aAbs.aStart, nRequestedLength, aArrays);
|
|
|
|
if (nArrayLength && !aArrays.empty() && !mxFormulaGroupContext)
|
|
{
|
|
//tdf#98880 if the DoubleVectorRefToken relies on the
|
|
//underlying storage provided by the Document
|
|
//FormulaGroupContext, take a reference to it here to
|
|
//ensure that backing storage exists for our lifetime
|
|
mxFormulaGroupContext = mrDoc.GetFormulaGroupContext();
|
|
}
|
|
}
|
|
break;
|
|
case svIndex:
|
|
{
|
|
if (p->GetOpCode() != ocName)
|
|
{
|
|
// May be DB-range or TableRef
|
|
mrGroupTokens.AddToken(*p);
|
|
break;
|
|
}
|
|
|
|
// Named range.
|
|
ScRangeName* pNames = mrDoc.GetRangeName();
|
|
if (!pNames)
|
|
// This should never fail.
|
|
return false;
|
|
|
|
ScRangeData* pRange = pNames->findByIndex(p->GetIndex());
|
|
if (!pRange)
|
|
// No named range exists by that index.
|
|
return false;
|
|
|
|
ScTokenArray* pNamedTokens = pRange->GetCode();
|
|
if (!pNamedTokens)
|
|
// This named range is empty.
|
|
return false;
|
|
|
|
mrGroupTokens.AddOpCode(ocOpen);
|
|
|
|
if (!convert(*pNamedTokens, rScope))
|
|
return false;
|
|
|
|
mrGroupTokens.AddOpCode(ocClose);
|
|
}
|
|
break;
|
|
default:
|
|
mrGroupTokens.AddToken(*p);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|