/** Store a matrix value in another matrix in the context of that other matrix istheresultmatrixofajumpmatrix.Allargumentsmustbevalidandare
not checked. */ staticvoid lcl_storeJumpMatResult( const ScMatrix* pMat, ScJumpMatrix* pJumpMat, SCSIZE nC, SCSIZE nR )
{ if ( pMat->IsValue( nC, nR ) )
{ double fVal = pMat->GetDouble( nC, nR );
pJumpMat->PutResultDouble( fVal, nC, nR );
} elseif ( pMat->IsEmpty( nC, nR ) )
{
pJumpMat->PutResultEmpty( nC, nR );
} else
{
pJumpMat->PutResultString(pMat->GetString(nC, nR), nC, nR);
}
}
void ScInterpreter::ScIfError( bool bNAonly )
{ constshort* pJump = pCur->GetJump(); short nJumpCount = pJump[ 0 ]; if (!sp || nJumpCount != 2)
{ // Reset nGlobalError here to not propagate the old error, if any.
nGlobalError = (sp ? FormulaError::ParameterExpected : FormulaError::UnknownStackVariable);
PushError( nGlobalError);
aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); return;
}
formula::ParamClass eReturnType = ScParameterClassification::GetParameterType( pCur, SAL_MAX_UINT16); if (eReturnType == ParamClass::Reference)
{ /* TODO: What about error handling and do we actually
* need the result matrix above at all in this case? */
ScComplexRefData aRef;
aRef.Ref1 = aRef.Ref2 = *(xRef->GetSingleRef());
pJumpMatrix->GetRefList().push_back( aRef);
}
} break; case svDoubleRef:
{ // upper left plus offset within matrix
FormulaConstTokenRef xRef = pStack[sp-1]; double fVal;
ScRange aRange;
PopDoubleRef( aRange ); if ( nGlobalError != FormulaError::NONE )
{
fVal = CreateDoubleError( nGlobalError );
nGlobalError = FormulaError::NONE;
pJumpMatrix->PutResultDouble( fVal, nC, nR );
} else
{ // Do not modify the original range because we use it // to adjust the size of the result matrix if necessary.
ScAddress aAdr( aRange.aStart);
sal_uLong nCol = static_cast<sal_uLong>(aAdr.Col()) + nC;
sal_uLong nRow = static_cast<sal_uLong>(aAdr.Row()) + nR; if ((nCol > o3tl::make_unsigned(aRange.aEnd.Col()) &&
aRange.aEnd.Col() != aRange.aStart.Col())
|| (nRow > o3tl::make_unsigned(aRange.aEnd.Row()) &&
aRange.aEnd.Row() != aRange.aStart.Row()))
{
fVal = CreateDoubleError( FormulaError::NotAvailable );
pJumpMatrix->PutResultDouble( fVal, nC, nR );
} else
{ // Replicate column and/or row of a vector if it is // one. Note that this could be a range reference // that in fact consists of only one cell, e.g. A1:A1 if (aRange.aEnd.Col() == aRange.aStart.Col())
nCol = aRange.aStart.Col(); if (aRange.aEnd.Row() == aRange.aStart.Row())
nRow = aRange.aStart.Row();
aAdr.SetCol( static_cast<SCCOL>(nCol) );
aAdr.SetRow( static_cast<SCROW>(nRow) );
ScRefCellValue aCell(mrDoc, aAdr); if (aCell.hasEmptyValue())
pJumpMatrix->PutResultEmpty( nC, nR ); elseif (aCell.hasNumeric())
{ double fCellVal = GetCellValue(aAdr, aCell); if ( nGlobalError != FormulaError::NONE )
{
fCellVal = CreateDoubleError(
nGlobalError);
nGlobalError = FormulaError::NONE;
}
pJumpMatrix->PutResultDouble( fCellVal, nC, nR );
} else
{
svl::SharedString aStr;
GetCellString(aStr, aCell); if ( nGlobalError != FormulaError::NONE )
{
pJumpMatrix->PutResultDouble( CreateDoubleError(
nGlobalError), nC, nR);
nGlobalError = FormulaError::NONE;
} else
pJumpMatrix->PutResultString(aStr, nC, nR);
}
}
SCSIZE nParmCols = aRange.aEnd.Col() - aRange.aStart.Col() + 1;
SCSIZE nParmRows = aRange.aEnd.Row() - aRange.aStart.Row() + 1;
lcl_AdjustJumpMatrix( pJumpMatrix, nParmCols, nParmRows );
}
formula::ParamClass eReturnType = ScParameterClassification::GetParameterType( pCur, SAL_MAX_UINT16); if (eReturnType == ParamClass::Reference)
{ /* TODO: What about error handling and do we actually
* need the result matrix above at all in this case? */
pJumpMatrix->GetRefList().push_back( *(xRef->GetDoubleRef()));
}
} break; case svExternalSingleRef:
{
ScExternalRefCache::TokenRef pToken;
PopExternalSingleRef(pToken); if (nGlobalError != FormulaError::NONE)
{
pJumpMatrix->PutResultDouble( CreateDoubleError( nGlobalError), nC, nR );
nGlobalError = FormulaError::NONE;
} else
{ switch (pToken->GetType())
{ case svDouble:
pJumpMatrix->PutResultDouble( pToken->GetDouble(), nC, nR ); break; case svString:
pJumpMatrix->PutResultString( pToken->GetString(), nC, nR ); break; case svEmptyCell:
pJumpMatrix->PutResultEmpty( nC, nR ); break; default: // svError was already handled (set by // PopExternalSingleRef()) with nGlobalError // above.
assert(!"unhandled svExternalSingleRef case");
pJumpMatrix->PutResultDouble( CreateDoubleError(
FormulaError::UnknownStackVariable), nC, nR );
}
}
} break; case svExternalDoubleRef: case svMatrix:
{ // match matrix offsets double fVal;
ScMatrixRef pMat = GetMatrix(); if ( nGlobalError != FormulaError::NONE )
{
fVal = CreateDoubleError( nGlobalError );
nGlobalError = FormulaError::NONE;
pJumpMatrix->PutResultDouble( fVal, nC, nR );
} elseif ( !pMat )
{
fVal = CreateDoubleError( FormulaError::UnknownVariable );
pJumpMatrix->PutResultDouble( fVal, nC, nR );
} else
{
SCSIZE nCols, nRows;
pMat->GetDimensions( nCols, nRows ); if ((nCols <= nC && nCols != 1) ||
(nRows <= nR && nRows != 1))
{
fVal = CreateDoubleError( FormulaError::NotAvailable );
pJumpMatrix->PutResultDouble( fVal, nC, nR );
} else
{ // GetMatrix() does SetErrorInterpreter() at the // matrix, do not propagate an error from // matrix->GetValue() as global error.
pMat->SetErrorInterpreter(nullptr);
lcl_storeJumpMatResult(pMat.get(), pJumpMatrix, nC, nR);
}
lcl_AdjustJumpMatrix( pJumpMatrix, nCols, nRows );
}
} break; case svError:
{
PopError(); double fVal = CreateDoubleError( nGlobalError);
nGlobalError = FormulaError::NONE;
pJumpMatrix->PutResultDouble( fVal, nC, nR );
} break; default:
{
Pop(); double fVal = CreateDoubleError( FormulaError::IllegalArgument);
pJumpMatrix->PutResultDouble( fVal, nC, nR );
}
}
}
} bool bCont = pJumpMatrix->Next( nC, nR ); if ( bCont )
{ double fBool; short nStart, nNext, nStop;
pJumpMatrix->GetJump( nC, nR, fBool, nStart, nNext, nStop ); while ( bCont && nStart == nNext )
{ // push all results that have no jump path if ( bHasResMat && (GetDoubleErrorValue( fBool) != FormulaError::JumpMatHasResult) )
{ // a false without path results in an empty path value if ( fBool == 0.0 )
pJumpMatrix->PutResultEmptyPath( nC, nR ); else
pJumpMatrix->PutResultDouble( fBool, nC, nR );
}
bCont = pJumpMatrix->Next( nC, nR ); if ( bCont )
pJumpMatrix->GetJump( nC, nR, fBool, nStart, nNext, nStop );
} if ( bCont && nStart != nNext )
{ const ScTokenVec & rParams = pJumpMatrix->GetJumpParameters(); for ( autoconst & i : rParams )
{ // This is not the current state of the interpreter, so // push without error, and elements' errors are coded into // double.
PushWithoutError(*i);
}
aCode.Jump( nStart, nNext, nStop );
}
} if ( !bCont )
{ // We're done with it, throw away jump matrix, keep result. // For an intermediate result of Reference use the array of references // if there are more than one reference and the current ForceArray // context is ReferenceOrRefArray. // Else (also for a final result of Reference) use the matrix. // Treat the result of a jump command as final and use the matrix (see // tdf#115493 for why). if (pCur->GetInForceArray() == ParamClass::ReferenceOrRefArray &&
pJumpMatrix->GetRefList().size() > 1 &&
ScParameterClassification::GetParameterType( pCur, SAL_MAX_UINT16) == ParamClass::Reference &&
!FormulaCompiler::IsOpCodeJumpCommand( pJumpMatrix->GetOpCode()) &&
aCode.PeekNextOperator())
{
FormulaTokenRef xRef = new ScRefListToken(true);
*(xRef->GetRefList()) = pJumpMatrix->GetRefList();
pJumpMatrix = nullptr;
Pop();
PushTokenRef( xRef);
maTokenMatrixMap.erase( pCur); // There's no result matrix to remember in this case.
} else
{
ScMatrix* pResMat = pJumpMatrix->GetResultMatrix();
pJumpMatrix = nullptr;
Pop();
PushMatrix( pResMat ); // Remove jump matrix from map and remember result matrix in case it // could be reused in another path of the same condition.
maTokenMatrixMap.erase( pCur);
maTokenMatrixMap.emplace(pCur, pStack[sp-1]);
} returntrue;
} returnfalse;
}
double ScInterpreter::Compare( ScQueryOp eOp )
{
sc::Compare aComp;
aComp.meOp = eOp;
aComp.mbIgnoreCase = mrDoc.GetDocOptions().IsIgnoreCase(); for( short i = 1; i >= 0; i-- )
{
sc::Compare::Cell& rCell = aComp.maCells[i];
switch ( GetRawStackType() )
{ case svEmptyCell:
Pop();
rCell.mbEmpty = true; break; case svMissing: case svDouble:
rCell.mfValue = GetDouble();
rCell.mbValue = true; break; case svString:
rCell.maStr = GetString();
rCell.mbValue = false; break; case svDoubleRef : case svSingleRef :
{
ScAddress aAdr; if ( !PopDoubleRefOrSingleRef( aAdr ) ) break;
ScRefCellValue aCell(mrDoc, aAdr); if (aCell.hasEmptyValue())
rCell.mbEmpty = true; elseif (aCell.hasString())
{
svl::SharedString aStr;
GetCellString(aStr, aCell);
rCell.maStr = std::move(aStr);
rCell.mbValue = false;
} else
{
rCell.mfValue = GetCellValue(aAdr, aCell);
rCell.mbValue = true;
}
} break; case svExternalSingleRef:
{
ScMatrixRef pMat = GetMatrix(); if (!pMat)
{
SetError( FormulaError::IllegalParameter); break;
}
SCSIZE nC, nR;
pMat->GetDimensions(nC, nR); if (!nC || !nR)
{
SetError( FormulaError::IllegalParameter); break;
} if (pMat->IsEmpty(0, 0))
rCell.mbEmpty = true; elseif (pMat->IsStringOrEmpty(0, 0))
{
rCell.maStr = pMat->GetString(0, 0);
rCell.mbValue = false;
} else
{
rCell.mfValue = pMat->GetDouble(0, 0);
rCell.mbValue = true;
}
} break; case svExternalDoubleRef: // TODO: Find out how to handle this... // Xcl generates a position dependent intersection using // col/row, as it seems to do for all range references, not // only in compare context. We'd need a general implementation // for that behavior similar to svDoubleRef in scalar and array // mode. Which also means we'd have to change all places where // it currently is handled along with svMatrix. default:
PopError();
SetError( FormulaError::IllegalParameter); break;
}
} if( nGlobalError != FormulaError::NONE ) return0;
nCurFmtType = nFuncFmtType = SvNumFormatType::LOGICAL; return sc::CompareFunc(aComp);
}
void ScInterpreter::ScRandomImpl( const std::function<double( double fFirst, double fLast )>& RandomFunc, double fFirst, double fLast )
{ if (bMatrixFormula)
{
SCCOL nCols = 0;
SCROW nRows = 0; // In JumpMatrix context use its dimensions for the return matrix; the // formula cell range selected may differ, for example if the result is // to be transposed. if (GetStackType(1) == svJumpMatrix)
{
SCSIZE nC, nR;
pStack[sp-1]->GetJumpMatrix()->GetDimensions( nC, nR);
nCols = std::max<SCCOL>(0, static_cast<SCCOL>(nC));
nRows = std::max<SCROW>(0, static_cast<SCROW>(nR));
} elseif (pMyFormulaCell)
pMyFormulaCell->GetMatColsRows( nCols, nRows);
if (nCols == 1 && nRows == 1)
{ // For compatibility with existing // com.sun.star.sheet.FunctionAccess.callFunction() calls that per // default are executed in array context unless // FA.setPropertyValue("IsArrayFunction",False) was set, return a // scalar double instead of a 1x1 matrix object. tdf#128218
PushDouble( RandomFunc( fFirst, fLast)); return;
}
// ScViewFunc::EnterMatrix() might be asking for // ScFormulaCell::GetResultDimensions(), which here are none so create // a 1x1 matrix at least which exactly is the case when EnterMatrix() // asks for a not selected range. if (nCols == 0)
nCols = 1; if (nRows == 0)
nRows = 1;
ScMatrixRef pResMat = GetNewMat( static_cast<SCSIZE>(nCols), static_cast<SCSIZE>(nRows), /*bEmpty*/true ); if (!pResMat)
PushError( FormulaError::MatrixSize); else
{ for (SCCOL i=0; i < nCols; ++i)
{ for (SCROW j=0; j < nRows; ++j)
{
pResMat->PutDouble( RandomFunc( fFirst, fLast), static_cast<SCSIZE>(i), static_cast<SCSIZE>(j));
}
}
PushMatrix( pResMat);
}
} else
{
PushDouble( RandomFunc( fFirst, fLast));
}
}
void ScInterpreter::ScRandArray()
{
sal_uInt8 nParamCount = GetByte(); // optional 5th para: // TRUE for a whole number // FALSE for a decimal number - default. bool bWholeNumber = false; if (nParamCount == 5)
bWholeNumber = GetBoolWithDefault(false);
// optional 4th para: The maximum value of the random numbers double fMax = 1.0; if (nParamCount >= 4)
fMax = GetDoubleWithDefault(1.0);
// optional 3rd para: The minimum value of the random numbers double fMin = 0.0; if (nParamCount >= 3)
fMin = GetDoubleWithDefault(0.0);
// optional 2nd para: The number of columns of the return array
SCCOL nCols = 1; if (nParamCount >= 2)
nCols = static_cast<SCCOL>(GetInt32WithDefault(1));
// optional 1st para: The number of rows of the return array
SCROW nRows = 1; if (nParamCount >= 1)
nRows = static_cast<SCROW>(GetInt32WithDefault(1));
// Follow the configurable string reference address syntax as also // used by INDIRECT() (and ADDRESS() for the sheet separator).
FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax; switch (eConv)
{ default: // Use the current address syntax if unspecified or says // one or the other or one we don't explicitly handle.
eConv = mrDoc.GetAddressConvention(); break; case FormulaGrammar::CONV_OOO: case FormulaGrammar::CONV_XL_A1: case FormulaGrammar::CONV_XL_R1C1: // Use that. break;
}
if ( aInfoType == "COL" )
PushInt(nCol + 1); elseif ( aInfoType == "ROW" )
PushInt(nRow + 1); elseif ( aInfoType == "SHEET" )
{ // For SHEET, No idea what number we should set, but let's always set // 1 if the external sheet exists, no matter what sheet. Excel does // the same. if (pRefMgr->getCacheTable(nFileId, aTabName, false))
PushInt(1); else
SetError(FormulaError::NoName);
} elseif ( aInfoType == "ADDRESS" )
{ // ODF 1.2 says we need to always display address using the ODF A1 grammar.
ScTokenArray aArray(mrDoc);
aArray.AddExternalSingleReference(nFileId, svl::SharedString( aTabName), aRef); // string not interned
ScCompiler aComp(mrDoc, aPos, aArray, formula::FormulaGrammar::GRAM_ODFF_A1);
OUString aStr;
aComp.CreateStringFromTokenArray(aStr);
PushString(aStr);
} elseif ( aInfoType == "FILENAME" )
{ const OUString* p = pRefMgr->getExternalFileName(nFileId); if (!p)
{ // In theory this should never happen...
SetError(FormulaError::NoName); return;
}
void ScInterpreter::ScTrim()
{ // Doesn't only trim but also removes duplicated blanks within!
OUString aVal = comphelper::string::strip(GetString().getString(), ' ');
OUStringBuffer aStr; const sal_Unicode* p = aVal.getStr(); const sal_Unicode* const pEnd = p + aVal.getLength(); while ( p < pEnd )
{ if ( *p != ' ' || p[-1] != ' ' ) // first can't be ' ', so -1 is fine
aStr.append(*p);
p++;
}
PushString(aStr.makeStringAndClear());
}
/* #i70213# fullwidth/halfwidth conversion provided by
* Takashi Nakamoto <bluedwarf@ooo>
* erAck: added Excel compatibility conversions as seen in issue's test case. */
static OUString lcl_convertIntoHalfWidth( const OUString & rStr )
{
// Make the initialization thread-safe. Since another function needs to be called, move it all to another
// function and thread-safely initialize a static reference in this function.
auto init = []() -> utl::TransliterationWrapper&
{
static utl::TransliterationWrapper trans( ::comphelper::getProcessComponentContext(), TransliterationFlags::NONE );
trans.loadModuleByImplName( u"FULLWIDTH_HALFWIDTH_LIKE_ASC"_ustr, LANGUAGE_SYSTEM );
return trans;
};
static utl::TransliterationWrapper& aTrans( init());
return aTrans.transliterate( rStr, 0, sal_uInt16( rStr.getLength() ) );
}
/* ODFF:
* Summary: Converts half-width to full-width ASCII and katakana characters.
* Semantics: Conversion is done for half-width ASCII and katakana characters,
* other characters are simply copied from T to the result. This is the
* complementary function to ASC.
* For references regarding halfwidth and fullwidth characters see
* http://www.unicode.org/reports/tr11/
* http://www.unicode.org/charts/charindex2.html#H
* http://www.unicode.org/charts/charindex2.html#F
*/
void ScInterpreter::ScJis()
{
if (MustHaveParamCount( GetByte(), 1))
PushString( lcl_convertIntoFullWidth( GetString().getString()));
}
/* ODFF:
* Summary: Converts full-width to half-width ASCII and katakana characters.
* Semantics: Conversion is done for full-width ASCII and katakana characters,
* other characters are simply copied from T to the result. This is the
* complementary function to JIS.
*/
void ScInterpreter::ScAsc()
{
if (MustHaveParamCount( GetByte(), 1))
PushString( lcl_convertIntoHalfWidth( GetString().getString()));
}
if (!xResMat)
{
// Create and init all elements with current value.
assert(nMatRows > 0);
xResMat = GetNewMat( 1, nMatRows, true);
xResMat->FillDouble( fCurrent, 0,0, 0,nMatRows-1);
}
else if (bDoMatOp)
{
// Current value and values from vector are operands
// for each vector position.
for (SCSIZE i=0; i < nMatRows; ++i)
{
MatOpFunc( i, fCurrent);
}
}
return true;
}
void ScInterpreter::ScMin( bool bTextAsZero )
{
short nParamCount = GetByte();
if (!MustHaveParamCountMin( nParamCount, 1))
return;
double nVal = 0.0;
ScAddress aAdr;
ScRange aRange;
size_t nRefInList = 0;
while (nParamCount-- > 0)
{
switch (GetStackType())
{
case svDouble :
{
nVal = GetDouble();
if (nMin > nVal) nMin = nVal;
nFuncFmtType = SvNumFormatType::NUMBER;
}
break;
case svSingleRef :
{
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
{
nVal = GetCellValue(aAdr, aCell);
CurFmtToFuncFmt();
if (nMin > nVal) nMin = nVal;
}
else if (bTextAsZero && aCell.hasString())
{
if ( nMin > 0.0 )
nMin = 0.0;
}
}
break;
case svRefList :
{
// bDoMatOp only for non-array value when switching to
// ArrayRefList.
if (SwitchToArrayRefList( xResMat, nMatRows, nMin, MatOpFunc,
nRefArrayPos == std::numeric_limits<size_t>::max()))
{
nRefArrayPos = nRefInList;
}
}
[[fallthrough]];
case svDoubleRef :
{
FormulaError nErr = FormulaError::NONE;
PopDoubleRef( aRange, nParamCount, nRefInList);
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero );
if (aValIter.GetFirst(nVal, nErr))
{
if (nMin > nVal)
nMin = nVal;
aValIter.GetCurNumFmtInfo( nFuncFmtType, nFuncFmtIndex );
while ((nErr == FormulaError::NONE) && aValIter.GetNext(nVal, nErr))
{
if (nMin > nVal)
nMin = nVal;
}
SetError(nErr);
}
if (nRefArrayPos != std::numeric_limits<size_t>::max())
{
// Update vector element with current value.
MatOpFunc( nRefArrayPos, nMin);
// Reset.
nMin = std::numeric_limits<double>::max();
nVal = 0.0;
nRefArrayPos = std::numeric_limits<size_t>::max();
}
}
break;
case svMatrix :
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (pMat)
{
nFuncFmtType = SvNumFormatType::NUMBER;
nVal = pMat->GetMinValue(bTextAsZero, bool(mnSubTotalFlags & SubtotalFlags::IgnoreErrVal));
if (nMin > nVal)
nMin = nVal;
}
}
break;
case svString :
{
Pop();
if ( bTextAsZero )
{
if ( nMin > 0.0 )
nMin = 0.0;
}
else
SetError(FormulaError::IllegalParameter);
}
break;
default :
PopError();
SetError(FormulaError::IllegalParameter);
}
}
if (xResMat)
{
// Include value of last non-references-array type and calculate final result.
if (nMin < std::numeric_limits<double>::max())
{
for (SCSIZE i=0; i < nMatRows; ++i)
{
MatOpFunc( i, nMin);
}
}
else
{
/* TODO: the awkward "no value is minimum 0.0" is likely the case
* if a value is numeric_limits::max. Still, that could be a valid
* minimum value as well, but nVal and nMin had been reset after
* the last svRefList... so we may lie here. */
for (SCSIZE i=0; i < nMatRows; ++i)
{
double fVecRes = xResMat->GetDouble(0,i);
if (fVecRes == std::numeric_limits<double>::max())
xResMat->PutDouble( 0.0, 0,i);
}
}
PushMatrix( xResMat);
}
else
{
if (!std::isfinite(nVal))
PushError( GetDoubleErrorValue( nVal));
else if ( nVal < nMin )
PushDouble(0.0); // zero or only empty arguments
else
PushDouble(nMin);
}
}
void ScInterpreter::ScMax( bool bTextAsZero )
{
short nParamCount = GetByte();
if (!MustHaveParamCountMin( nParamCount, 1))
return;
double nVal = 0.0;
ScAddress aAdr;
ScRange aRange;
size_t nRefInList = 0;
while (nParamCount-- > 0)
{
switch (GetStackType())
{
case svDouble :
{
nVal = GetDouble();
if (nMax < nVal) nMax = nVal;
nFuncFmtType = SvNumFormatType::NUMBER;
}
break;
case svSingleRef :
{
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
{
nVal = GetCellValue(aAdr, aCell);
CurFmtToFuncFmt();
if (nMax < nVal) nMax = nVal;
}
else if (bTextAsZero && aCell.hasString())
{
if ( nMax < 0.0 )
nMax = 0.0;
}
}
break;
case svRefList :
{
// bDoMatOp only for non-array value when switching to
// ArrayRefList.
if (SwitchToArrayRefList( xResMat, nMatRows, nMax, MatOpFunc,
nRefArrayPos == std::numeric_limits<size_t>::max()))
{
nRefArrayPos = nRefInList;
}
}
[[fallthrough]];
case svDoubleRef :
{
FormulaError nErr = FormulaError::NONE;
PopDoubleRef( aRange, nParamCount, nRefInList);
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero );
if (aValIter.GetFirst(nVal, nErr))
{
if (nMax < nVal)
nMax = nVal;
aValIter.GetCurNumFmtInfo( nFuncFmtType, nFuncFmtIndex );
while ((nErr == FormulaError::NONE) && aValIter.GetNext(nVal, nErr))
{
if (nMax < nVal)
nMax = nVal;
}
SetError(nErr);
}
if (nRefArrayPos != std::numeric_limits<size_t>::max())
{
// Update vector element with current value.
MatOpFunc( nRefArrayPos, nMax);
// Reset.
nMax = std::numeric_limits<double>::lowest();
nVal = 0.0;
nRefArrayPos = std::numeric_limits<size_t>::max();
}
}
break;
case svMatrix :
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (pMat)
{
nFuncFmtType = SvNumFormatType::NUMBER;
nVal = pMat->GetMaxValue(bTextAsZero, bool(mnSubTotalFlags & SubtotalFlags::IgnoreErrVal));
if (nMax < nVal)
nMax = nVal;
}
}
break;
case svString :
{
Pop();
if ( bTextAsZero )
{
if ( nMax < 0.0 )
nMax = 0.0;
}
else
SetError(FormulaError::IllegalParameter);
}
break;
default :
PopError();
SetError(FormulaError::IllegalParameter);
}
}
if (xResMat)
{
// Include value of last non-references-array type and calculate final result.
if (nMax > std::numeric_limits<double>::lowest())
{
for (SCSIZE i=0; i < nMatRows; ++i)
{
MatOpFunc( i, nMax);
}
}
else
{
/* TODO: the awkward "no value is maximum 0.0" is likely the case
* if a value is numeric_limits::lowest. Still, that could be a
* valid maximum value as well, but nVal and nMax had been reset
* after the last svRefList... so we may lie here. */
for (SCSIZE i=0; i < nMatRows; ++i)
{
double fVecRes = xResMat->GetDouble(0,i);
if (fVecRes == -std::numeric_limits<double>::max())
xResMat->PutDouble( 0.0, 0,i);
}
}
PushMatrix( xResMat);
}
else
{
if (!std::isfinite(nVal))
PushError( GetDoubleErrorValue( nVal));
else if ( nVal > nMax )
PushDouble(0.0); // zero or only empty arguments
else
PushDouble(nMax);
}
}
/* this was: PushDouble( sqrt( div( nVal, nValCount)));
*
* Besides that the special NAN gets lost in the call through sqrt(),
* unxlngi6.pro then looped back and forth somewhere between div() and
* ::rtl::math::setNan(). Tests showed that
*
* sqrt( div( 1, 0));
*
* produced a loop, but
*
* double f1 = div( 1, 0);
* sqrt( f1 );
*
* was fine. There seems to be some compiler optimization problem. It does
* not occur when compiled with debug=t.
*/
}
/** returns -1 when the matrix value is smaller than the query value, 0 when
they are equal, and 1 when the matrix value is larger than the query
value. */
sal_Int32 lcl_CompareMatrix2Query( SCSIZE i, const VectorMatrixAccessor& rMat, const ScQueryParam& rParam,
const ScQueryEntry& rEntry, bool bMatchWholeCell, bool bEmptyIsLess = true )
{
if (rMat.IsEmpty(i))
{
/* TODO: in case we introduced query for real empty this would have to
* be changed! */
if (bEmptyIsLess)
return -1; // empty always less than anything else
else
return 1; // empty always greater than anything else
}
/* FIXME: what is an empty path (result of IF(false;true_path) in
* comparisons? */
bool bByString = rEntry.GetQueryItem().meType == ScQueryEntry::ByString;
if (rMat.IsValue(i))
{
const double nVal1 = rMat.GetDouble(i);
if (!std::isfinite(nVal1))
{
// XXX Querying for error values is not required, otherwise we'd
// need to check here.
return 1; // error always greater than numeric or string
}
if (bByString)
return -1; // numeric always less than string
const double nVal2 = rEntry.GetQueryItem().mfVal;
// XXX Querying for error values is not required, otherwise we'd need
// to check here and move that check before the bByString check.
if (nVal1 == nVal2)
return 0;
return nVal1 < nVal2 ? -1 : 1;
}
if (!bByString)
return 1; // string always greater than numeric
bool bMatch = rEntry.GetSearchTextPtr(rParam.eSearchType, rParam.bCaseSens, bMatchWholeCell)
->SearchForward(aStr1, &nStart, &nEnd);
// from 614 on, nEnd is behind the found text
if (bMatch && bMatchWholeCell
&& (nStart != 0 || nEnd != aStr1.getLength()))
bMatch = false; // RegExp must match entire cell string
bool bOk = ((rEntry.eOp == SC_NOT_EQUAL) ? !bMatch : bMatch);
if (bOk)
return 0; // we have a WildOrRegExp match
}
/** returns -1 when matrix(i) value is smaller than matrix(j) value, 0 when
they are equal, and 1 when larger */
sal_Int32 lcl_Compare2MatrixCells( SCSIZE i, const VectorMatrixAccessor& rMat, SCSIZE j )
{
// empty always less than anything else
if (rMat.IsEmpty(i))
return ( rMat.IsEmpty(j) ? 0 : -1 );
else if (rMat.IsEmpty(j))
return 1;
bool bByString = rMat.IsStringOrEmpty(j); // string, empty has already been handled
if (rMat.IsValue(i))
{
const double nVal1 = rMat.GetDouble(i);
if (!std::isfinite(nVal1))
return 1; // error always greater than numeric or string
if (bByString)
return -1; // numeric always less than string
// get search value
if (nGlobalError == FormulaError::NONE)
{
switch (GetRawStackType())
{
case svMissing:
case svEmptyCell:
{
vsa.isEmptySearch = true;
vsa.isStringSearch = false;
vsa.sSearchStr = GetString();
}
break;
case svDouble:
{
vsa.isStringSearch = false;
vsa.fSearchVal = GetDouble();
}
break;
case svString:
{
vsa.isStringSearch = true;
vsa.sSearchStr = GetString();
}
break;
case svDoubleRef:
case svSingleRef:
{
ScAddress aAdr;
if (!PopDoubleRefOrSingleRef(aAdr))
{
PushInt(0);
return;
}
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
{
vsa.isStringSearch = false;
vsa.fSearchVal = GetCellValue(aAdr, aCell);
}
else
{
vsa.isStringSearch = true;
GetCellString(vsa.sSearchStr, aCell);
}
}
break;
case svExternalSingleRef:
{
ScExternalRefCache::TokenRef pToken;
PopExternalSingleRef(pToken);
if (nGlobalError != FormulaError::NONE)
{
PushError(nGlobalError);
return;
}
if (pToken->GetType() == svDouble)
{
vsa.isStringSearch = false;
vsa.fSearchVal = pToken->GetDouble();
}
else
{
vsa.isStringSearch = true;
vsa.sSearchStr = pToken->GetString();
}
}
break;
case svExternalDoubleRef:
case svMatrix:
{
ScMatValType nType = GetDoubleOrStringFromMatrix(
vsa.fSearchVal, vsa.sSearchStr);
vsa.isStringSearch = ScMatrix::IsNonValueType(nType);
}
break;
default:
{
PushIllegalParameter();
return;
}
}
// execute search
if (SearchVectorForValue(vsa))
PushDouble(vsa.nIndex);
else
{
if (vsa.isResultNA)
PushNA();
else
return; // error occurred and has already been pushed
}
}
else
PushIllegalParameter();
}
namespace {
bool isCellContentEmpty( const ScRefCellValue& rCell )
{
switch (rCell.getType())
{
case CELLTYPE_VALUE:
case CELLTYPE_STRING:
case CELLTYPE_EDIT:
return false;
case CELLTYPE_FORMULA:
{
// NOTE: Excel treats ="" in a referenced cell as blank in
// COUNTBLANK() but not in ISBLANK(), which is inconsistent.
// COUNTBLANK() tests the (display) result whereas ISBLANK() tests
// the cell content.
// ODFF allows both for COUNTBLANK().
// OOo and LibreOffice prior to 4.4 did not treat ="" as blank in
// COUNTBLANK(), we now do for Excel interoperability.
/* TODO: introduce yet another compatibility option? */
sc::FormulaResultValue aRes = rCell.getFormula()->GetResult();
if (aRes.meType != sc::FormulaResultValue::String)
return false;
if (!aRes.maString.isEmpty())
return false;
}
break;
default:
;
}
ScCellIterator aIter( mrDoc, aRange, mnSubTotalFlags);
for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
{
const ScRefCellValue& rCell = aIter.getRefCellValue();
if (!isCellContentEmpty(rCell))
++nCount;
}
if (xResMat)
{
xResMat->PutDouble( nMaxCount - nCount, 0, nRefListArrayPos);
nMaxCount = nCount = 0;
}
}
}
break;
case svMatrix:
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef xMat = GetMatrix();
if (!xMat)
SetError( FormulaError::IllegalParameter);
else
{
SCSIZE nC, nR;
xMat->GetDimensions( nC, nR);
nMaxCount = nC * nR;
// Numbers (implicit), strings and error values, ignore empty
// strings as those if not entered in an inline array are the
// result of a formula, to be par with a reference to formula
// cell as *visual* blank, see isCellContentEmpty() above.
nCount = xMat->Count( true, true, true);
}
}
break;
default : SetError(FormulaError::IllegalParameter); break;
}
if (xResMat)
PushMatrix( xResMat);
else
PushDouble(nMaxCount - nCount);
}
ScMatrixRef pSumExtraMatrix;
bool bSumExtraRange = (nParamCount == 3);
if (bSumExtraRange)
{
// Save only the upperleft cell in case of cell range. The geometry
// of the 3rd parameter is taken from the 1st parameter.
KahanSum fSum = 0.0;
double fRes = 0.0;
double fCount = 0.0;
short nParam = 1;
const SCSIZE nMatRows = GetRefListArrayMaxSize( nParam);
// There's either one RefList and nothing else, or none.
ScMatrixRef xResMat = (nMatRows ? GetNewMat( 1, nMatRows, /*bEmpty*/true ) : nullptr);
SCSIZE nRefListArrayPos = 0;
size_t nRefInList = 0;
while (nParam-- > 0)
{
SCCOL nCol1 = 0;
SCROW nRow1 = 0;
SCTAB nTab1 = 0;
SCCOL nCol2 = 0;
SCROW nRow2 = 0;
SCTAB nTab2 = 0;
ScMatrixRef pQueryMatrix;
switch ( GetStackType() )
{
case svRefList :
if (bSumExtraRange)
{
/* TODO: this could resolve if all refs are of the same size */
SetError( FormulaError::IllegalParameter);
}
else
{
nRefListArrayPos = nRefInList;
ScRange aRange;
PopDoubleRef( aRange, nParam, nRefInList);
aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
}
break;
case svDoubleRef :
PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
break;
case svSingleRef :
PopSingleRef( nCol1, nRow1, nTab1 );
nCol2 = nCol1;
nRow2 = nRow1;
nTab2 = nTab1;
break;
case svMatrix:
case svExternalSingleRef:
case svExternalDoubleRef:
{
pQueryMatrix = GetMatrix();
if (!pQueryMatrix)
{
PushError( FormulaError::IllegalParameter);
return;
}
nCol1 = 0;
nRow1 = 0;
nTab1 = 0;
SCSIZE nC, nR;
pQueryMatrix->GetDimensions( nC, nR);
nCol2 = static_cast<SCCOL>(nC - 1);
nRow2 = static_cast<SCROW>(nR - 1);
nTab2 = 0;
}
break;
default:
SetError( FormulaError::IllegalParameter);
}
if ( nTab1 != nTab2 )
{
SetError( FormulaError::IllegalParameter);
}
if (bSumExtraRange)
{ // Take the range geometry of the 1st parameter and apply it to // the 3rd. If parts of the resulting range would point outside // the sheet, don't complain but silently ignore and simply cut // them away, this is what Xcl does :-/
// For the cut-away part we also don't need to determine the // criteria match, so shrink the source range accordingly, // instead of the result range.
SCCOL nColDelta = nCol2 - nCol1;
SCROW nRowDelta = nRow2 - nRow1;
SCCOL nMaxCol;
SCROW nMaxRow; if (pSumExtraMatrix)
{
SCSIZE nC, nR;
pSumExtraMatrix->GetDimensions( nC, nR);
nMaxCol = static_cast<SCCOL>(nC - 1);
nMaxRow = static_cast<SCROW>(nR - 1);
} else
{
nMaxCol = mrDoc.MaxCol();
nMaxRow = mrDoc.MaxRow();
} if (nCol3 + nColDelta > nMaxCol)
{
SCCOL nNewDelta = nMaxCol - nCol3;
nCol2 = nCol1 + nNewDelta;
}
std::vector<sal_uInt8>& vConditions = mrContext.maConditions; // vConditions is cached, although it is clear'ed after every cell is interpreted, // if the SUMIFS/COUNTIFS are part of a matrix formula, then that is not enough because // with a single InterpretTail() call it results in evaluation of all the cells in the // matrix formula.
vConditions.clear();
bool bHasDoubleRefCriteriaRanges = true; // Do not attempt main-range reduce if any of the criteria-ranges are not double-refs. // For COUNTIFS queries it's possible to range-reduce too, if the query is not supposed // to match empty cells (will be checked and undone later if needed), so simply treat // the first criteria range as the main range for purposes of detecting if this can be done. for (sal_uInt16 nParamIdx = 2; nParamIdx < nParamCount; nParamIdx += 2 )
{ const formula::FormulaToken* pCriteriaRangeToken = pStack[ sp-nParamIdx ]; if (pCriteriaRangeToken->GetType() != svDoubleRef )
{
bHasDoubleRefCriteriaRanges = false; break;
}
}
// Probe the main range token, and try if we can shrink the range without altering results. const formula::FormulaToken* pMainRangeToken = pStack[ sp-nParamCount ]; if (pMainRangeToken->GetType() == svDoubleRef && bHasDoubleRefCriteriaRanges)
{ const ScComplexRefData* pRefData = pMainRangeToken->GetDoubleRef(); if (!pRefData->IsDeleted())
{
DoubleRefToRange( *pRefData, aMainRange); if (aMainRange.aStart.Tab() == aMainRange.aEnd.Tab())
{ // Shrink the range to actual data content.
ScRange aSubRange = aMainRange;
mrDoc.GetDataAreaSubrange(aSubRange);
nStartColDiff = aSubRange.aStart.Col() - aMainRange.aStart.Col();
nStartRowDiff = aSubRange.aStart.Row() - aMainRange.aStart.Row();
nEndColDiff = aSubRange.aEnd.Col() - aMainRange.aEnd.Col();
nEndRowDiff = aSubRange.aEnd.Row() - aMainRange.aEnd.Row();
bRangeReduce = nStartColDiff || nStartRowDiff || nEndColDiff || nEndRowDiff;
}
}
}
// Undo bRangeReduce if asked to match empty cells for COUNTIFS (which should be rare).
assert(rEntry.GetQueryItems().size() == 1); constbool isCountIfs = (nParamCount % 2) == 0; if(isCountIfs && (rEntry.IsQueryByEmpty() || rItem.mbMatchEmpty) && bRangeReduce)
{
bRangeReduce = false; // All criteria ranges are svDoubleRef's, so only vConditions needs adjusting.
assert(vRefArrayConditions.empty()); if(!vConditions.empty())
{
std::vector<sal_uInt8> newConditions;
SCCOL newDimensionCols = nCol2 - nCol1 + 1;
SCROW newDimensionRows = nRow2 - nRow1 + 1;
newConditions.reserve( newDimensionCols * newDimensionRows );
SCCOL col = nCol1; for(; col < nCol1 + nStartColDiff; ++col)
newConditions.insert( newConditions.end(), newDimensionRows, 0 ); for(; col <= nCol2 - nStartColDiff; ++col)
{
newConditions.insert( newConditions.end(), nStartRowDiff, 0 );
SCCOL oldCol = col - ( nCol1 + nStartColDiff );
size_t nIndex = oldCol * nDimensionRows; if (nIndex < vConditions.size())
{ auto it = vConditions.begin() + nIndex;
newConditions.insert( newConditions.end(), it, it + nDimensionRows );
} else
newConditions.insert( newConditions.end(), nDimensionRows, 0 );
newConditions.insert( newConditions.end(), -nEndRowDiff, 0 );
} for(; col <= nCol2; ++col)
newConditions.insert( newConditions.end(), newDimensionRows, 0 );
assert( newConditions.size() == o3tl::make_unsigned( newDimensionCols * newDimensionRows ));
vConditions = std::move( newConditions );
nDimensionCols = newDimensionCols;
nDimensionRows = newDimensionRows;
}
}
if (bRangeReduce)
{ // All reference ranges must be of the same size as the main range. if( aMainRange.aEnd.Col() - aMainRange.aStart.Col() != nCol2 - nCol1
|| aMainRange.aEnd.Row() - aMainRange.aStart.Row() != nRow2 - nRow1)
{
PushError ( FormulaError::IllegalArgument); return;
}
nCol1 += nStartColDiff;
nRow1 += nStartRowDiff;
nCol2 += nEndColDiff;
nRow2 += nEndRowDiff;
}
// All reference ranges must be of same dimension and size. if (!nDimensionCols)
nDimensionCols = nCol2 - nCol1 + 1; if (!nDimensionRows)
nDimensionRows = nRow2 - nRow1 + 1; if ((nDimensionCols != (nCol2 - nCol1 + 1)) || (nDimensionRows != (nRow2 - nRow1 + 1)))
{
PushError ( FormulaError::IllegalArgument); return;
}
// result matrix is filled with boolean values.
std::vector<double> aResValues;
pResultMatrix->GetDoubleArray(aResValues); if (vConditions.size() != aResValues.size())
{
PushError( FormulaError::IllegalParameter); return;
}
std::vector<double>::const_iterator itThisRes = aResValues.begin(); for (auto& rCondition : vConditions)
{
rCondition += *itThisRes;
++itThisRes;
}
} else
{ if( ScQueryCellIteratorSortedCache::CanBeUsed( mrDoc, rParam, nTab1, pMyFormulaCell,
refData, mrContext ))
{
ScQueryCellIteratorSortedCache aCellIter(mrDoc, mrContext, nTab1, rParam, false, false); // Increment Entry.nField in iterator when switching to next column.
aCellIter.SetAdvanceQueryParamEntryField( true ); if ( aCellIter.GetFirst() )
{ do
{
size_t nC = aCellIter.GetCol() + nColDiff;
size_t nR = aCellIter.GetRow() + nRowDiff;
++vConditions[nC * nDimensionRows + nR];
} while ( aCellIter.GetNext() );
}
} else
{
ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, rParam, false, false); // Increment Entry.nField in iterator when switching to next column.
aCellIter.SetAdvanceQueryParamEntryField( true ); if ( aCellIter.GetFirst() )
{ do
{
size_t nC = aCellIter.GetCol() + nColDiff;
size_t nR = aCellIter.GetRow() + nRowDiff;
++vConditions[nC * nDimensionRows + nR];
} while ( aCellIter.GetNext() );
}
}
} if (nRefArrayPos != std::numeric_limits<size_t>::max())
{ // Apply condition result to reference list array result position.
std::vector<sal_uInt8>& rVec = vRefArrayConditions[nRefArrayPos]; if (rVec.empty())
rVec = vConditions; else
{
assert(rVec.size() == vConditions.size()); // see dimensions above for (size_t i=0, n = rVec.size(); i < n; ++i)
{
rVec[i] += vConditions[i];
}
} // Reset conditions vector. // When leaving an svRefList this has to be emptied not set to // 0.0 because it's checked when entering an svRefList. if (nRefInList == 0)
std::vector<sal_uInt8>().swap( vConditions); else
std::for_each( vConditions.begin(), vConditions.end(), [](sal_uInt8 & r){ r = 0; } );
}
}
nParamCount -= 2;
}
if (!vRefArrayConditions.empty() && !vConditions.empty())
{ // Add/op the last current value to all array positions. for (auto & rVec : vRefArrayConditions)
{ if (rVec.empty())
rVec = vConditions; else
{
assert(rVec.size() == vConditions.size()); // see dimensions above for (size_t i=0, n = rVec.size(); i < n; ++i)
{
rVec[i] += vConditions[i];
}
}
}
}
if (nGlobalError != FormulaError::NONE)
{
PushError( nGlobalError); return; // bail out
}
sc::ParamIfsResult aRes;
ScMatrixRef xResMat;
// main range - only for AVERAGEIFS, SUMIFS, MINIFS and MAXIFS if (nParamCount == 1)
{ short nParam = nParamCount;
size_t nRefInList = 0;
size_t nRefArrayPos = std::numeric_limits<size_t>::max(); bool bRefArrayMain = false; while (nParam-- == nParamCount)
{
SCCOL nMainCol1 = 0;
SCROW nMainRow1 = 0;
SCTAB nMainTab1 = 0;
SCCOL nMainCol2 = 0;
SCROW nMainRow2 = 0;
SCTAB nMainTab2 = 0;
ScMatrixRef pMainMatrix; switch ( GetStackType() )
{ case svRefList :
{ const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]); if (p && p->IsArrayResult())
{ if (vRefArrayConditions.empty())
{ // Replicate conditions if there wasn't a // reference list array for criteria // evaluation.
vRefArrayConditions.resize( nRefArrayRows); for (auto & rVec : vRefArrayConditions)
{
rVec = vConditions;
}
}
// All reference ranges must be of same dimension and size. if ((nDimensionCols != (nMainCol2 - nMainCol1 + 1)) || (nDimensionRows != (nMainRow2 - nMainRow1 + 1)))
{
PushError ( FormulaError::IllegalArgument); return;
}
if (nGlobalError != FormulaError::NONE)
{
PushError( nGlobalError); return; // bail out
}
// end-result calculation
// This gets weird... if conditions were calculated using a // reference list array but the main calculation range is not a // reference list array, then the conditions of the array are // applied to the main range each in turn to form the array result.
// The third parameter, result array, double, string and reference. double fResVal = 0.0;
svl::SharedString aResStr;
StackVar eResArrayType = svUnknown;
if (nParamCount == 3)
{
eResArrayType = GetStackType(); switch (eResArrayType)
{ case svDoubleRef:
{
SCTAB nTabJunk;
PopDoubleRef(nResCol1, nResRow1, nResTab,
nResCol2, nResRow2, nTabJunk); if (nResTab != nTabJunk ||
((nResRow2 - nResRow1) > 0 && (nResCol2 - nResCol1) > 0))
{ // The result array must be a vector.
PushIllegalParameter(); return;
}
} break; case svSingleRef:
PopSingleRef( nResCol1, nResRow1, nResTab);
nResCol2 = nResCol1;
nResRow2 = nResRow1; break; case svMatrix: case svExternalSingleRef: case svExternalDoubleRef:
{
pResMat = GetMatrix(); if (!pResMat)
{
PushIllegalParameter(); return;
}
SCSIZE nC, nR;
pResMat->GetDimensions(nC, nR); if (nC != 1 && nR != 1)
{ // Result matrix must be a vector.
PushIllegalParameter(); return;
}
nResCol2 = nC - 1;
nResRow2 = nR - 1;
} break; case svDouble:
fResVal = GetDouble(); break; case svString:
aResStr = GetString(); break; default:
PushIllegalParameter(); return;
}
}
// For double, string and single reference. double fDataVal = 0.0;
svl::SharedString aDataStr;
ScAddress aDataAdr; bool bValueData = false;
// Get the data-result range and also determine whether this is vertical // lookup or horizontal lookup.
if ( eDataArrayType == svDouble || eDataArrayType == svString ||
eDataArrayType == svSingleRef )
{ // Delta position for a single value is always 0.
// Found if data <= query, but not if query is string and found data is // numeric or vice versa. This is how Excel does it but doesn't // document it.
if (pResMat)
{ if (pResMat->IsValue( 0, 0 ))
PushDouble(pResMat->GetDouble( 0, 0 )); else
PushString(pResMat->GetString(0, 0));
} elseif (nParamCount == 3)
{ switch (eResArrayType)
{ case svDouble:
PushDouble( fResVal ); break; case svString:
PushString( aResStr ); break; case svDoubleRef: case svSingleRef:
PushCellResultToken( true, ScAddress( nResCol1, nResRow1, nResTab), nullptr, nullptr); break; default:
assert(!"ScInterpreter::ScLookup: unhandled eResArrayType, single value data");
PushIllegalParameter();
}
} else
{ switch (eDataArrayType)
{ case svDouble:
PushDouble( fDataVal ); break; case svString:
PushString( aDataStr ); break; case svSingleRef:
PushCellResultToken( true, aDataAdr, nullptr, nullptr); break; default:
assert(!"ScInterpreter::ScLookup: unhandled eDataArrayType, single value data");
PushIllegalParameter();
}
} return;
}
// Now, perform the search to compute the delta position (nDelta).
if (pDataMat)
{ // Data array is given as a matrix.
rEntry.bDoQuery = true;
rEntry.eOp = SC_LESS_EQUAL; bool bFound = false;
SCSIZE nC, nR;
pDataMat->GetDimensions(nC, nR);
// Do not propagate errors from matrix while copying to vector.
pDataMat->SetErrorInterpreter( nullptr);
// Excel has an undocumented behaviour in that it seems to internally // sort an interim array (i.e. error values specifically #DIV/0! are // sorted to the end) or ignore error values that makes these "get last // non-empty" searches work, e.g. =LOOKUP(2,1/NOT(ISBLANK(A:A)),A:A) // see tdf#117016 // Instead of sorting a million entries of which mostly only a bunch of // rows are filled and moving error values to the end which most are // already anyway, assume the matrix to be sorted except error values // and omit the coded DoubleError values. // Do this only for a numeric matrix (that includes errors coded as // doubles), which covers the case in question. /* TODO: it's unclear whether this really matches Excel behaviour in *allconstellationsoriftherearecasesthatincludeunsortederror *valuesandthusyieldarbitrarybinarysearchresultsorsomething *differentorwhethertherearecaseswhereerrorvaluesarealso *omittedfrommixednumeric/stringarraysorifit'snotaninterim
* matrix but a cell range reference instead. */ constbool bOmitErrorValues = (eDataArrayType == svMatrix && pDataMat->IsNumeric());
// In case of non-vector matrix, only search the first row or column.
ScMatrixRef pDataMat2;
std::vector<SCCOLROW> vIndex; if (bOmitErrorValues)
{
std::vector<double> vArray;
VectorMatrixAccessor aMatAcc(*pDataMat, bVertical); const SCSIZE nElements = aMatAcc.GetElementCount(); for (SCSIZE i=0; i < nElements; ++i)
{ constdouble fVal = aMatAcc.GetDouble(i); if (std::isfinite(fVal))
{
vArray.push_back(fVal);
vIndex.push_back(i);
}
} if (vArray.empty())
{
PushNA(); return;
} const size_t nElems = vArray.size(); if (nElems == nElements)
{ // No error value omitted, use as is.
pDataMat2 = pDataMat;
std::vector<SCCOLROW>().swap( vIndex);
} else
{
nLenMajor = nElems; if (bVertical)
{
ScMatrixRef pTempMat = GetNewMat( 1, nElems, /*bEmpty*/true );
pTempMat->PutDoubleVector( vArray, 0, 0);
pDataMat2 = std::move(pTempMat);
} else
{
ScMatrixRef pTempMat = GetNewMat( nElems, 1, /*bEmpty*/true ); for (size_t i=0; i < nElems; ++i)
pTempMat->PutDouble( vArray[i], i, 0);
pDataMat2 = std::move(pTempMat);
}
}
} else
{ // Just use as is with the VectorMatrixAccessor.
pDataMat2 = pDataMat;
}
// Do not propagate errors from matrix while searching.
pDataMat2->SetErrorInterpreter( nullptr);
SCSIZE nFirst = 0, nLast = nLenMajor-1; //, nHitIndex = 0;
for (SCSIZE nLen = nLast-nFirst; nLen > 0; nLen = nLast-nFirst)
{
SCSIZE nMid = nFirst + nLen/2;
sal_Int32 nCmp = lcl_CompareMatrix2Query( nMid, aMatAcc2, aParam, rEntry, bMatchWholeCell,
false /* bEmptyIsLess, instead empty are sorted to end */);
if (nCmp == 0)
{
// exact match. find the last item with the same value.
lcl_GetLastMatch( nMid, aMatAcc2, nLenMajor);
nDelta = nMid;
bFound = true;
break;
}
if (nLen == 1) // first and last items are next to each other.
{
nDelta = nCmp < 0 ? nLast - 1 : nFirst - 1;
// If already the 1st item is greater there's nothing found.
bFound = (nDelta >= 0);
break;
}
if (nCmp < 0)
nFirst = nMid;
else
nLast = nMid;
}
if (nDelta == static_cast<SCCOLROW>(nLenMajor-2)) // last item
{
sal_Int32 nCmp = lcl_CompareMatrix2Query(nDelta+1, aMatAcc2, aParam, rEntry, bMatchWholeCell,
false /* bEmptyIsLess, instead empty are sorted to end */);
if (nCmp <= 0)
{
// either the last item is an exact match or the real
// hit is beyond the last item.
nDelta += 1;
bFound = true;
}
}
else if (nDelta > 0) // valid hit must be 2nd item or higher
{
// non-exact match
bFound = true;
}
// With 0-9 < A-Z, if query is numeric and data found is string, or
// vice versa, the (yet another undocumented) Excel behavior is to
// return #N/A instead.
if (bFound)
{
if (!vIndex.empty())
nDelta = vIndex[nDelta];
VectorMatrixAccessor aMatAcc(*pDataMat, bVertical);
SCCOLROW i = nDelta;
SCSIZE n = aMatAcc.GetElementCount();
if (o3tl::make_unsigned(i) >= n) i = static_cast<SCCOLROW>(n);
bool bByString = rEntry.GetQueryItem().meType == ScQueryEntry::ByString;
if (bByString == aMatAcc.IsValue(i))
bFound = false;
}
if (!bFound)
{
PushNA();
return;
}
// Now that we've found the delta, push the result back to the cell.
if (pResMat)
{
VectorMatrixAccessor aResMatAcc(*pResMat, (nResRow2 - nResRow1) > 0);
// Result array is matrix.
// Note this does not replicate the other dimension.
if (o3tl::make_unsigned(nDelta) >= aResMatAcc.GetElementCount())
{
PushNA();
return;
}
if (aResMatAcc.IsValue(nDelta))
PushDouble(aResMatAcc.GetDouble(nDelta));
else
PushString(aResMatAcc.GetString(nDelta));
}
else if (nParamCount == 3)
{
/* TODO: the entire switch is a copy of the cell range search
* result, factor out. */
switch (eResArrayType)
{
case svDoubleRef:
case svSingleRef:
{
// Use the result array vector. Note that the result array is assumed
// to be a vector (i.e. 1-dimensional array).
ScAddress aAdr;
aAdr.SetTab(nResTab);
bool bResVertical = (nResRow2 - nResRow1) > 0;
if (bResVertical)
{
SCROW nTempRow = static_cast<SCROW>(nResRow1 + nDelta);
if (nTempRow > mrDoc.MaxRow())
{
PushDouble(0);
return;
}
aAdr.SetCol(nResCol1);
aAdr.SetRow(nTempRow);
}
else
{
SCCOL nTempCol = static_cast<SCCOL>(nResCol1 + nDelta);
if (nTempCol > mrDoc.MaxCol())
{
PushDouble(0);
return;
}
aAdr.SetCol(nTempCol);
aAdr.SetRow(nResRow1);
}
PushCellResultToken( true, aAdr, nullptr, nullptr);
}
break;
case svDouble:
case svString:
{
if (nDelta != 0)
PushNA();
else
{
switch (eResArrayType)
{
case svDouble:
PushDouble( fResVal );
break;
case svString:
PushString( aResStr );
break;
default:
; // nothing
}
}
}
break;
default:
assert(!"ScInterpreter::ScLookup: unhandled eResArrayType, array search");
PushIllegalParameter();
}
}
else
{
// No result array. Use the data array to get the final value from.
// Propagate errors from matrix again.
pDataMat->SetErrorInterpreter( this);
if (bVertical)
{
if (pDataMat->IsValue(nC-1, nDelta))
PushDouble(pDataMat->GetDouble(nC-1, nDelta));
else
PushString(pDataMat->GetString(nC-1, nDelta));
}
else
{
if (pDataMat->IsValue(nDelta, nR-1))
PushDouble(pDataMat->GetDouble(nDelta, nR-1));
else
PushString(pDataMat->GetString(nDelta, nR-1));
}
}
if (pResMat)
{
VectorMatrixAccessor aResMatAcc(*pResMat, (nResRow2 - nResRow1) > 0);
// Use the matrix result array.
// Note this does not replicate the other dimension.
if (o3tl::make_unsigned(nDelta) >= aResMatAcc.GetElementCount())
{
PushNA();
return;
}
if (aResMatAcc.IsValue(nDelta))
PushDouble(aResMatAcc.GetDouble(nDelta));
else
PushString(aResMatAcc.GetString(nDelta));
}
else if (nParamCount == 3)
{
/* TODO: the entire switch is a copy of the array search result, factor
* out. */
switch (eResArrayType)
{
case svDoubleRef:
case svSingleRef:
{
// Use the result array vector. Note that the result array is assumed
// to be a vector (i.e. 1-dimensional array).
ScAddress aAdr;
aAdr.SetTab(nResTab);
bool bResVertical = (nResRow2 - nResRow1) > 0;
if (bResVertical)
{
SCROW nTempRow = static_cast<SCROW>(nResRow1 + nDelta);
if (nTempRow > mrDoc.MaxRow())
{
PushDouble(0);
return;
}
aAdr.SetCol(nResCol1);
aAdr.SetRow(nTempRow);
}
else
{
SCCOL nTempCol = static_cast<SCCOL>(nResCol1 + nDelta);
if (nTempCol > mrDoc.MaxCol())
{
PushDouble(0);
return;
}
aAdr.SetCol(nTempCol);
aAdr.SetRow(nResRow1);
}
PushCellResultToken( true, aAdr, nullptr, nullptr);
}
break;
case svDouble:
case svString:
{
if (nDelta != 0)
PushNA();
else
{
switch (eResArrayType)
{
case svDouble:
PushDouble( fResVal );
break;
case svString:
PushString( aResStr );
break;
default:
; // nothing
}
}
}
break;
default:
assert(!"ScInterpreter::ScLookup: unhandled eResArrayType, range search");
PushIllegalParameter();
}
}
else
{
// Regardless of whether or not the result array exists, the last
// array is always used as the "result" array.
void ScInterpreter::ScXLookup()
{
/* TODO
-use VectorSearchArguments and SearchVectorForValue() with ScLookup, ScHLookup and ScVLookup
as well to reduce redundant code, can de done later with lots of other MATCH/LOOKUP related code
that can be unified
-BinarySearch not supported for columns (horizontal search), now just use linear mode in this case
-improve efficiency of code
*/
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 3, 6 ) )
return;
if ( nParamCount == 6 )
{
sal_Int16 k = GetInt16();
if ( k >= -2 && k <= 2 && k != 0 )
vsa.eSearchMode = static_cast<LookupSearchMode>(k);
else
{
PushIllegalParameter();
return;
}
}
else
vsa.eSearchMode = LookupSearchMode::Forward;
if ( nParamCount >= 5 )
{
sal_Int16 k = GetInt16();
if ( k >= -1 && k <= 3 )
vsa.eMatchMode = static_cast<MatchMode>(k);
else
{
PushIllegalParameter();
return;
}
}
else
vsa.eMatchMode = exactorNA;
// Optional 4th argument to set return values if not found (default is #N/A)
formula::FormulaConstTokenRef xNotFound;
FormulaError nFirstMatchError = FormulaError::NONE;
if ( nParamCount >= 4 && GetStackType() != svEmptyCell )
{
xNotFound = PopToken();
nFirstMatchError = xNotFound->GetError();
nGlobalError = FormulaError::NONE; // propagate only for match or active result path
}
default:
PushIllegalParameter();
return;
}
if ( ( nsR >= nsC && nsR != nrR ) || ( nsR < nsC && nsC != nrC ) )
{
// search matrix must have same number of elements as result matrix in search direction
PushIllegalParameter();
return;
}
// 1st argument is search value
if (nGlobalError == FormulaError::NONE)
{
switch ( GetRawStackType() )
{
case svMissing:
case svEmptyCell:
{
vsa.isEmptySearch = true;
vsa.isStringSearch = false;
vsa.sSearchStr = GetString();
}
break;
// Optional 3th argument to set the value to return if all values
// in the included array are empty (filter returns nothing)
formula::FormulaConstTokenRef xNotFound;
if (nParamCount == 3 && GetStackType() != svEmptyCell)
xNotFound = PopToken();
SCCOL nCondResultColEnd = 0;
SCROW nCondResultRowEnd = 0;
ScMatrixRef pCondResultMatrix = nullptr;
std::vector<double> aResValues;
size_t nMatch = 0;
// take 2nd argument criteria bool array
switch ( GetStackType() )
{
case svMatrix :
case svExternalDoubleRef:
case svExternalSingleRef:
case svDoubleRef:
case svSingleRef:
{
pCondResultMatrix = GetMatrix();
if (!pCondResultMatrix)
{
PushError(FormulaError::IllegalParameter);
return;
}
SCSIZE nC, nR;
pCondResultMatrix->GetDimensions(nC, nR);
nCondResultColEnd = static_cast<SCCOL>(nC - 1);
nCondResultRowEnd = static_cast<SCROW>(nR - 1);
// only 1 dimension of filtering allowed (also in excel)
if (nCondResultColEnd > 0 && nCondResultRowEnd > 0)
{
PushError(FormulaError::NoValue);
return;
}
// result matrix is filled with boolean values.
pCondResultMatrix->GetDoubleArray(aResValues);
FormulaError nError = FormulaError::NONE;
auto matchNum = [&nMatch, &nError](double i) {
nError = GetDoubleErrorValue(i);
if (nError != FormulaError::NONE)
{
return true;
}
else
{
if (i > 0)
nMatch++;
return false;
}
};
if (auto it = std::find_if(aResValues.begin(), aResValues.end(), matchNum); it != aResValues.end())
{
PushError(nError);
return;
}
}
break;
default:
{
PushIllegalParameter();
return;
}
}
// bail out, no need to evaluate other arguments
if (nGlobalError != FormulaError::NONE)
{
PushError(nGlobalError);
return;
}
SCCOL nQueryCol1 = 0;
SCROW nQueryRow1 = 0;
SCCOL nQueryCol2 = 0;
SCROW nQueryRow2 = 0;
ScMatrixRef pQueryMatrix = nullptr;
// take 1st argument range
switch ( GetStackType() )
{
case svSingleRef:
case svDoubleRef:
case svMatrix:
case svExternalSingleRef:
case svExternalDoubleRef:
{
pQueryMatrix = GetMatrix();
if (!pQueryMatrix)
{
PushError( FormulaError::IllegalParameter);
return;
}
SCSIZE nC, nR;
pQueryMatrix->GetDimensions( nC, nR);
nQueryCol2 = static_cast<SCCOL>(nC - 1);
nQueryRow2 = static_cast<SCROW>(nR - 1);
}
break;
default:
PushError( FormulaError::IllegalParameter);
return;
}
// bail out, no need to set a matrix if we have no result
if (!nMatch)
{
if (xNotFound && (xNotFound->GetType() != svMissing))
PushTokenRef(xNotFound);
else
PushError(FormulaError::NestedArray);
return;
}
SCSIZE nResPos = 0;
ScMatrixRef pResMat = nullptr;
if (nQueryCol2 == nCondResultColEnd && nCondResultColEnd > 0)
{
pResMat = GetNewMat(nMatch, nQueryRow2 + 1 , /*bEmpty*/true);
for (SCROW iR = nQueryRow1; iR <= nQueryRow2; iR++)
{
for (size_t iC = 0; iC < aResValues.size(); iC++)
{
if (aResValues[iC] > 0)
{
if (pQueryMatrix->IsEmptyCell(iC, iR))
pResMat->PutEmptyTrans(nResPos++);
else if (pQueryMatrix->IsStringOrEmpty(iC, iR))
pResMat->PutStringTrans(pQueryMatrix->GetString(iC, iR), nResPos++);
else
pResMat->PutDoubleTrans(pQueryMatrix->GetDouble(iC, iR), nResPos++);
}
}
}
}
else if (nQueryRow2 == nCondResultRowEnd && nCondResultRowEnd > 0)
{
pResMat = GetNewMat(nQueryCol2 + 1, nMatch, /*bEmpty*/true);
for (SCCOL iC = nQueryCol1; iC <= nQueryCol2; iC++)
{
for (size_t iR = 0; iR < aResValues.size(); iR++)
{
if (aResValues[iR] > 0)
{
if (pQueryMatrix->IsEmptyCell(iC, iR))
pResMat->PutEmpty(nResPos++);
else if (pQueryMatrix->IsStringOrEmpty(iC, iR))
pResMat->PutString(pQueryMatrix->GetString(iC, iR), nResPos++);
else
pResMat->PutDouble(pQueryMatrix->GetDouble(iC, iR), nResPos++);
}
}
}
}
else
{
PushError(FormulaError::IllegalParameter);
return;
}
if (pResMat)
PushMatrix(pResMat);
else
PushError(FormulaError::NestedArray);
}
// fill result matrix to the same column
for (SCSIZE iPos = 0; iPos < aResPos.size(); ++iPos)
{
if (bCol)
lcl_FillCell(pMatSource, pResMat, aResPos[iPos].first, aResPos[iPos].second, 0, iPos);
else
lcl_FillCell(pMatSource, pResMat, aResPos[iPos].first, aResPos[iPos].second, iPos, 0);
}
// We must fish the 1st parameter deep from the stack! And push it on top.
const FormulaToken* p = pStack[ sp - nParamCount ];
PushWithoutError( *p );
sal_Int32 nFunc = GetInt32();
mnSubTotalFlags |= SubtotalFlags::IgnoreNestedStAg | SubtotalFlags::IgnoreFiltered;
if (nFunc > 100)
{
// For opcodes 101 through 111, we need to skip hidden cells.
// Other than that these opcodes are identical to 1 through 11.
mnSubTotalFlags |= SubtotalFlags::IgnoreHidden;
nFunc -= 100;
}
if ( nGlobalError != FormulaError::NONE || nFunc < 1 || nFunc > 11 )
PushIllegalArgument(); // simulate return on stack, not SetError(...)
else
{
cPar = nParamCount - 1;
switch( nFunc )
{
case SUBTOTAL_FUNC_AVE : ScAverage(); break;
case SUBTOTAL_FUNC_CNT : ScCount(); break;
case SUBTOTAL_FUNC_CNT2 : ScCount2(); break;
case SUBTOTAL_FUNC_MAX : ScMax(); break;
case SUBTOTAL_FUNC_MIN : ScMin(); break;
case SUBTOTAL_FUNC_PROD : ScProduct(); break;
case SUBTOTAL_FUNC_STD : ScStDev(); break;
case SUBTOTAL_FUNC_STDP : ScStDevP(); break;
case SUBTOTAL_FUNC_SUM : ScSum(); break;
case SUBTOTAL_FUNC_VAR : ScVar(); break;
case SUBTOTAL_FUNC_VARP : ScVarP(); break;
default : PushIllegalArgument(); break;
}
}
mnSubTotalFlags = SubtotalFlags::NONE;
// Get rid of the 1st (fished) parameter.
FormulaConstTokenRef xRef( PopToken());
Pop();
PushTokenRef( xRef);
}
// 1st argument: take range
ScMatrixRef pMatSource = nullptr;
SCSIZE nsC = 0, nsR = 0;
switch (GetStackType())
{
case svSingleRef:
case svDoubleRef:
case svMatrix:
case svExternalSingleRef:
case svExternalDoubleRef:
{
pMatSource = GetMatrix();
if (!pMatSource)
{
PushIllegalParameter();
return;
}
// fish the 1st parameter from the stack and push it on top.
const FormulaToken* p = pStack[ sp - nParamCount ];
PushWithoutError( *p );
sal_Int32 nFunc = GetInt32();
// fish the 2nd parameter from the stack and push it on top.
const FormulaToken* p2 = pStack[ sp - ( nParamCount - 1 ) ];
PushWithoutError( *p2 );
sal_Int32 nOption = GetInt32();
if (nGlobalError != FormulaError::NONE || !pDBRef)
return nullptr;
if ( bRangeFake )
{
// range parameter must match entire database range
if (pDBRef->isRangeEqual(aMissingRange))
rMissingField = true;
else
SetError( FormulaError::IllegalParameter );
}
if (nGlobalError != FormulaError::NONE)
return nullptr;
SCCOL nField = pDBRef->getFirstFieldColumn();
if (rMissingField)
; // special case
else if (bByVal)
nField = pDBRef->findFieldColumn(static_cast<SCCOL>(nVal));
else
{
FormulaError nErr = FormulaError::NONE;
nField = pDBRef->findFieldColumn(aStr.getString(), &nErr);
SetError(nErr);
}
if (pParam)
{
// An allowed missing field parameter sets the result field
// to any of the query fields, just to be able to return
// some cell from the iterator.
if ( rMissingField )
nField = static_cast<SCCOL>(pParam->GetEntry(0).nField);
pParam->mnField = nField;
SCSIZE nCount = pParam->GetEntryCount();
for ( SCSIZE i=0; i < nCount; i++ )
{
ScQueryEntry& rEntry = pParam->GetEntry(i);
if (!rEntry.bDoQuery)
break;
void ScInterpreter::ScDBCount()
{
bool bMissingField = true;
unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) );
if (pQueryParam)
{
sal_uLong nCount = 0;
if ( bMissingField && pQueryParam->GetType() == ScDBQueryParamBase::INTERNAL )
{ // count all matching records
// TODO: currently the QueryIterators only return cell pointers of
// existing cells, so if a query matches an empty cell there's
// nothing returned, and therefore not counted!
// Since this has ever been the case and this code here only came
// into existence to fix #i6899 and it never worked before we'll
// have to live with it until we reimplement the iterators to also
// return empty cells, which would mean to adapt all callers of
// iterators.
ScDBQueryParamInternal* p = static_cast<ScDBQueryParamInternal*>(pQueryParam.get());
p->nCol2 = p->nCol1; // Don't forget to select only one column.
SCTAB nTab = p->nTab;
// ScQueryCellIteratorDirect doesn't make use of ScDBQueryParamBase::mnField,
// so the source range has to be restricted, like before the introduction
// of ScDBQueryParamBase.
p->nCol1 = p->nCol2 = p->mnField;
ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab, *p, true, false);
if ( aCellIter.GetFirst() )
{
do
{
nCount++;
} while ( aCellIter.GetNext() );
}
}
else
{ // count only matching records with a value in the "result" field
if (!pQueryParam->IsValidFieldIndex())
{
SetError(FormulaError::NoValue);
return;
}
ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam));
ScDBQueryDataIterator::Value aValue;
if ( aValIter.GetFirst(aValue) && aValue.mnError == FormulaError::NONE )
{
do
{
nCount++;
}
while ( aValIter.GetNext(aValue) && aValue.mnError == FormulaError::NONE );
}
SetError(aValue.mnError);
}
PushDouble( nCount );
}
else
PushIllegalParameter();
}
// Reference address syntax for INDIRECT is configurable.
FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax;
if (eConv == FormulaGrammar::CONV_UNSPECIFIED)
// Use the current address syntax if unspecified.
eConv = mrDoc.GetAddressConvention();
// either CONV_A1_XL_A1 was explicitly configured, or it wasn't possible
// to determine which syntax to use during doc import
bool bTryXlA1 = (eConv == FormulaGrammar::CONV_A1_XL_A1);
if (nParamCount == 2 && 0.0 == GetDouble() )
{
// Overwrite the config and try Excel R1C1.
eConv = FormulaGrammar::CONV_XL_R1C1;
bTryXlA1 = false;
}
svl::SharedString sSharedRefStr = GetString();
const OUString & sRefStr = sSharedRefStr.getString();
if (sRefStr.isEmpty())
{
// Bail out early for empty cells, rely on "we do have a string" below.
PushError( FormulaError::NoRef);
return;
}
// Named expressions and DB range names need to be tried first, as older 1K
// columns allowed names that would now match a 16k columns cell address.
do
{
ScRangeData* pData = ScRangeStringConverter::GetRangeDataFromString( sRefStr, nTab, mrDoc, eConv);
if (!pData)
break;
// We need this in order to obtain a good range.
pData->ValidateTabRefs();
ScRange aRange;
// This is the usual way to treat named ranges containing
// relative references.
if (!pData->IsReference(aRange, aPos))
{
sTabRefStr = pData->GetSymbol();
bTableRefNamed = lcl_IsTableStructuredRef(sTabRefStr, nTableRefNamedIndex);
// if bTableRefNamed is true, we have a name that maps to a table structured reference.
// Such a case is handled below.
break;
}
// In Excel, specifying a table name without [] resolves to the
// same as with [], a range that excludes header and totals
// rows and contains only data rows. Do the same.
if (pData->HasHeader())
aRange.aStart.IncRow();
if (pData->HasTotals())
aRange.aEnd.IncRow(-1);
if (aRange.aStart.Row() > aRange.aEnd.Row())
break;
ScRefAddress aRefAd, aRefAd2;
ScAddress::ExternalInfo aExtInfo;
if ( !bTableRefNamed &&
(ConvertDoubleRef(mrDoc, sRefStr, nTab, aRefAd, aRefAd2, aDetails, &aExtInfo) ||
( bTryXlA1 && ConvertDoubleRef(mrDoc, sRefStr, nTab, aRefAd,
aRefAd2, aDetailsXlA1, &aExtInfo) ) ) )
{
if (aExtInfo.mbExternal)
{
PushExternalDoubleRef(
aExtInfo.mnFileId, aExtInfo.maTabName,
aRefAd.Col(), aRefAd.Row(), aRefAd.Tab(),
aRefAd2.Col(), aRefAd2.Row(), aRefAd2.Tab());
}
else
PushDoubleRef( aRefAd, aRefAd2);
}
else if ( !bTableRefNamed &&
(ConvertSingleRef(mrDoc, sRefStr, nTab, aRefAd, aDetails, &aExtInfo) ||
( bTryXlA1 && ConvertSingleRef (mrDoc, sRefStr, nTab, aRefAd,
aDetailsXlA1, &aExtInfo) ) ) )
{
if (aExtInfo.mbExternal)
{
PushExternalSingleRef(
aExtInfo.mnFileId, aExtInfo.maTabName, aRefAd.Col(), aRefAd.Row(), aRefAd.Tab());
}
else
PushSingleRef( aRefAd);
}
else
{
// It may be even a TableRef or an external name.
// Anything else that resolves to one reference could be added
// here, but we don't want to compile every arbitrary string. This
// is already nasty enough...
sal_Int32 nIndex = bTableRefNamed ? nTableRefNamedIndex : -1;
bool bTableRef = bTableRefNamed;
if (!bTableRefNamed)
bTableRef = lcl_IsTableStructuredRef(sRefStr, nIndex);
bool bExternalName = false; // External references would had been consumed above already.
if (!bTableRef)
{
// This is our own file name reference representation centric.. but
// would work also for XL '[doc]'!name and also for
// '[doc]Sheet1'!name ... sickos.
if (sRefStr[0] == '\'')
{
// Minimum 'a'#name or 'a'!name
// bTryXlA1 means try both, first our own.
if (bTryXlA1 || eConv == FormulaGrammar::CONV_OOO)
{
nIndex = ScGlobal::FindUnquoted( sRefStr, '#');
if (nIndex >= 3 && sRefStr[nIndex-1] == '\'')
{
bExternalName = true;
eConv = FormulaGrammar::CONV_OOO;
}
}
if (!bExternalName && (bTryXlA1 || eConv != FormulaGrammar::CONV_OOO))
{
nIndex = ScGlobal::FindUnquoted( sRefStr, '!');
if (nIndex >= 3 && sRefStr[nIndex-1] == '\'')
{
bExternalName = true;
}
}
}
}
if (bExternalName || bTableRef)
{
do
{
ScCompiler aComp( mrDoc, aPos, mrDoc.GetGrammar());
aComp.SetRefConvention( eConv); // must be after grammar
std::unique_ptr<ScTokenArray> pTokArr( aComp.CompileString(bTableRefNamed ? sTabRefStr : sRefStr));
if (pTokArr->GetCodeError() != FormulaError::NONE || !pTokArr->GetLen())
break;
// Whatever... use only the specific case.
if (bExternalName)
{
const formula::FormulaToken* pTok = pTokArr->FirstToken();
if (!pTok || pTok->GetType() != svExternalName)
break;
}
else if (!pTokArr->HasOpCode( ocTableRef))
break;
aComp.CompileTokenArray();
// A syntactically valid reference will generate exactly
// one RPN token, a reference or error. Discard everything
// else as error.
if (pTokArr->GetCodeLen() != 1)
break;
ScTokenRef xTok( pTokArr->FirstRPNToken());
if (!xTok)
break;
switch (xTok->GetType())
{
case svSingleRef:
case svDoubleRef:
case svExternalSingleRef:
case svExternalDoubleRef:
case svError:
PushTokenRef( xTok);
// success!
return;
default:
; // nothing
}
}
while (false);
}
FormulaGrammar::AddressConvention eConv = FormulaGrammar::CONV_OOO; // default
if (nParamCount >= 4 && 0.0 == GetDoubleWithDefault( 1.0))
eConv = FormulaGrammar::CONV_XL_R1C1;
else
{
// If A1 syntax is requested then the actual sheet separator and format
// convention depends on the syntax configured for INDIRECT to match
// that, and if it is unspecified then the document's address syntax.
FormulaGrammar::AddressConvention eForceConv = maCalcConfig.meStringRefAddressSyntax;
if (eForceConv == FormulaGrammar::CONV_UNSPECIFIED)
eForceConv = mrDoc.GetAddressConvention();
if (eForceConv == FormulaGrammar::CONV_XL_A1 || eForceConv == FormulaGrammar::CONV_XL_R1C1)
eConv = FormulaGrammar::CONV_XL_A1; // for anything Excel use Excel A1
}
// Access one element of a vector independent of col/row
// orientation. Excel documentation does not mention, but
// i62850 had a .xls example of a row vector accessed by
// row number returning one element. This
// INDEX(row_vector;element) behaves the same as
// INDEX(row_vector;0;element) and thus contradicts Excel
// documentation where the second parameter is always
// row_num.
//
// ODFF v1.3 in 6.14.6 INDEX states "If DataSource is a
// one-dimensional row vector, Row is optional, which
// effectively makes Row act as the column offset into the
// vector". Guess the first Row is a typo and should read
// Column instead.
// Flags are supported only for replacement, search match flags can be // individually and much more flexible set in the regular expression // pattern using (?ismwx-ismwx) bool bGlobalReplacement = false;
sal_Int32 nOccurrence = 1; // default first occurrence, if any if (nParamCount == 4)
{ // Argument can be either string or double. double fOccurrence;
svl::SharedString aFlagsString; bool bDouble; if (!IsMissing())
bDouble = GetDoubleOrString( fOccurrence, aFlagsString); else
{ // For an omitted argument keep the default.
PopError();
bDouble = true;
fOccurrence = nOccurrence;
} if (nGlobalError != FormulaError::NONE)
{
PushError( nGlobalError); return;
} if (bDouble)
{ if (!CheckStringPositionArgument( fOccurrence))
{
PushError( FormulaError::IllegalArgument); return;
}
nOccurrence = static_cast<sal_Int32>(fOccurrence);
} else
{ const OUString& aFlags( aFlagsString.getString()); // Empty flags string is valid => no flag set. if (aFlags.getLength() > 1)
{ // Only one flag supported.
PushIllegalArgument(); return;
} if (aFlags.getLength() == 1)
{ if (aFlags.indexOf('g') >= 0)
bGlobalReplacement = true; else
{ // Unsupported flag.
PushIllegalArgument(); return;
}
}
}
}
bool bReplacement = false;
OUString aReplacement; if (nParamCount >= 3)
{ // A missing argument is not an empty string to replace the match. // nOccurrence==0 forces no replacement, so simply discard the // argument. if (IsMissing() || nOccurrence == 0)
PopError(); else
{
aReplacement = GetString().getString();
bReplacement = true;
}
} // If bGlobalReplacement==true and bReplacement==false then // bGlobalReplacement is silently ignored.
if (!bReplacement)
{ // Find n-th occurrence.
sal_Int32 nCount = 0; while (aRegexMatcher.find(status) && U_SUCCESS(status) && ++nCount < nOccurrence)
; if (U_FAILURE(status))
{ // Some error.
PushIllegalArgument(); return;
} // n-th match found? if (nCount != nOccurrence)
{
PushError( FormulaError::NotAvailable); return;
} // Extract matched text.
icu::UnicodeString aMatch( aRegexMatcher.group( status)); if (U_FAILURE(status))
{ // Some error.
PushIllegalArgument(); return;
}
OUString aResult( reinterpret_cast<const sal_Unicode*>(aMatch.getBuffer()), aMatch.length());
PushString( aResult); return;
}
const icu::UnicodeString aIcuReplacement( false, reinterpret_cast<const UChar*>(aReplacement.getStr()), aReplacement.getLength());
icu::UnicodeString aReplaced; if (bGlobalReplacement) // Replace all occurrences of match with replacement.
aReplaced = aRegexMatcher.replaceAll( aIcuReplacement, status); elseif (nOccurrence == 1) // Replace first occurrence of match with replacement.
aReplaced = aRegexMatcher.replaceFirst( aIcuReplacement, status); else
{ // Replace n-th occurrence of match with replacement.
sal_Int32 nCount = 0; while (aRegexMatcher.find(status) && U_SUCCESS(status))
{ // XXX NOTE: After several RegexMatcher::find() the // RegexMatcher::appendReplacement() still starts at the // beginning (or after the last appendReplacement() position // which is none here) and copies the original text up to the // current found match and then replaces the found match. if (++nCount == nOccurrence)
{
aRegexMatcher.appendReplacement( aReplaced, aIcuReplacement, status); break;
}
}
aRegexMatcher.appendTail( aReplaced);
} if (U_FAILURE(status))
{ // Some error, e.g. extraneous $1 without group.
PushIllegalArgument(); return;
}
OUString aResult( reinterpret_cast<const sal_Unicode*>(aReplaced.getBuffer()), aReplaced.length());
PushString( aResult);
}
sal_Int32 nCnt; if (nParamCount == 4)
{
nCnt = GetStringPositionArgument(); if (nCnt < 1)
{
PushIllegalArgument(); return;
}
} else
nCnt = 0;
OUString sNewStr = GetString().getString();
OUString sOldStr = GetString().getString();
OUString sStr = GetString().getString();
sal_Int32 nPos = 0;
sal_Int32 nCount = 0;
std::optional<OUStringBuffer> oResult; for (sal_Int32 nEnd = sStr.indexOf(sOldStr); nEnd >= 0; nEnd = sStr.indexOf(sOldStr, nEnd))
{ if (nCnt == 0 || ++nCount == nCnt) // Found a replacement cite
{ if (!oResult) // Only allocate buffer when needed
oResult.emplace(sStr.getLength() + sNewStr.getLength() - sOldStr.getLength());
oResult->append(sStr.subView(nPos, nEnd - nPos)); // Copy leading unchanged text if (!CheckStringResultLen(*oResult, sNewStr.getLength())) return PushError(GetError());
oResult->append(sNewStr); // Copy the replacement
nPos = nEnd + sOldStr.getLength(); if (nCnt > 0) // Found the single replacement site - end the loop break;
}
nEnd += sOldStr.getLength();
} if (oResult) // If there were prior replacements, copy the rest, otherwise use original
oResult->append(sStr.subView(nPos, sStr.getLength() - nPos));
PushString(oResult ? oResult->makeStringAndClear() : sStr);
}
staticbool MayBeRegExp( std::u16string_view rStr )
{ if ( rStr.empty() || (rStr.size() == 1 && rStr[0] != '.') ) returnfalse; // single meta characters can not be a regexp // First two characters are wildcard '?' and '*' characters.
std::u16string_view cre(u"?*+.[]^$\\<>()|"); return rStr.find_first_of(cre) != std::u16string_view::npos;
}
staticbool MayBeWildcard( std::u16string_view rStr )
{ // Wildcards with '~' escape, if there are no wildcards then an escaped // character does not make sense, but it modifies the search pattern in an // Excel compatible wildcard search...
std::u16string_view cw(u"*?~"); return rStr.find_first_of(cw) != std::u16string_view::npos;
}
switch ( vsa.eSearchMode )
{ case LookupSearchMode::Forward :
{ switch ( vsa.eMatchMode )
{ case exactorNA : case wildcard : // simple serial search for equality mode (source data doesn't // need to be sorted). for (SCSIZE i = 0; i < nMatCount; ++i)
{ if (lcl_CompareMatrix2Query( i, aMatAcc, rParam, rEntry, bMatchWholeCell ) == 0)
{
vsa.nHitIndex = i+1; // found ! break;
}
} break;
case exactorS : case exactorG : for (SCSIZE i = 0; i < nMatCount; ++i)
{
sal_Int32 result = lcl_CompareMatrix2Query( i, aMatAcc, rParam, rEntry, bMatchWholeCell ); if (result == 0)
{
vsa.nHitIndex = i+1; // found ! break;
} elseif (vsa.eMatchMode == exactorS && result == -1)
{ if ( vsa.nBestFit == SCSIZE_MAX )
vsa.nBestFit = i; else
{ // replace value of vsa.nBestFit if value(i) > value(vsa.nBestFit) if ( lcl_Compare2MatrixCells( i, aMatAcc, vsa.nBestFit) == 1 )
vsa.nBestFit = i;
}
} elseif (vsa.eMatchMode == exactorG && result == 1)
{ if ( vsa.nBestFit == SCSIZE_MAX )
vsa.nBestFit = i; else
{ // replace value of vsa.nBestFit if value(i) < value(vsa.nBestFit) if ( lcl_Compare2MatrixCells( i, aMatAcc, vsa.nBestFit) == -1 )
vsa.nBestFit = i;
}
} // else do nothing
} break;
case LookupSearchMode::Reverse:
{ switch ( vsa.eMatchMode )
{ case exactorNA : case wildcard : // simple serial search for equality mode (source data doesn't // need to be sorted). for ( SCSIZE i = nMatCount; i > 0; i-- )
{ if (lcl_CompareMatrix2Query(i - 1, aMatAcc, rParam, rEntry, bMatchWholeCell) == 0)
{
vsa.nHitIndex = i; // found ! break;
}
} break;
case exactorS : case exactorG : for (SCSIZE i = nMatCount - 1; i-- > 0; )
{
sal_Int32 result = lcl_CompareMatrix2Query( i, aMatAcc, rParam, rEntry, bMatchWholeCell ); if (result == 0)
{
vsa.nHitIndex = i + 1; // found ! break;
} elseif (vsa.eMatchMode == exactorS && result == -1)
{ if ( vsa.nBestFit == SCSIZE_MAX )
vsa.nBestFit = i; else
{ // replace value of vsa.nBestFit if value(i) > value(vsa.nBestFit) if ( lcl_Compare2MatrixCells( i, aMatAcc, vsa.nBestFit) == 1 )
vsa.nBestFit = i;
}
} elseif (vsa.eMatchMode == exactorG && result == 1)
{ if ( vsa.nBestFit == SCSIZE_MAX )
vsa.nBestFit = i; else
{ // replace value of vsa.nBestFit if value(i) < value(vsa.nBestFit) if ( lcl_Compare2MatrixCells( i, aMatAcc, vsa.nBestFit) == -1 )
vsa.nBestFit = i;
}
} // else do nothing
} break;
case exactorS :
rEntry.eOp = SC_LESS_EQUAL; break;
case exactorG :
rEntry.eOp = SC_GREATER_EQUAL; break;
case wildcard : case regex : // this mode can only used with XLOOKUP/XMATCH if ( vsa.nSearchOpCode == SC_OPCODE_X_LOOKUP || vsa.nSearchOpCode == SC_OPCODE_X_MATCH )
{ // Wildcard/Regex search mode with binary search is not allowed if (vsa.eSearchMode == LookupSearchMode::BinaryAscending || vsa.eSearchMode == LookupSearchMode::BinaryDescending)
{
PushNoValue(); returnfalse;
}
ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); // allow to match empty cells as result if we are looking for the next smaller // or larger values in case of the new lookup functions if (rEntry.eOp != SC_EQUAL && (vsa.nSearchOpCode == SC_OPCODE_X_LOOKUP ||
vsa.nSearchOpCode == SC_OPCODE_X_MATCH))
rItem.mbMatchEmpty = true;
// tdf#121052: // =VLOOKUP(SearchCriterion; RangeArray; Index; Sorted) // [SearchCriterion] is the value searched for in the first column of the array. // [RangeArray] is the reference, which is to comprise at least two columns. // [Index] is the number of the column in the array that contains the value to be returned. The first column has the number 1. // // Prerequisite of lcl_getPrevRowWithEmptyValueLookup(): // Value referenced by [SearchCriterion] is empty. // lcl_getPrevRowWithEmptyValueLookup() performs following checks: // - if we run query with "exact match" mode (i.e. VLOOKUP) // - and if we already have the same lookup done before but for another row // which is also had empty [SearchCriterion] // // then // we could say, that for current row we could reuse results of the cached call which was done for the row2 // In this case we return row index, which is >= 0. // // Elsewhere // -1 is returned, which will lead to default behavior => // complete lookup will be done in RangeArray inside lcl_LookupQuery() method. // // This method was added only for speed up to avoid several useless complete // lookups inside [RangeArray] for searching empty strings. // static SCROW lcl_getPrevRowWithEmptyValueLookup( const ScLookupCache& rCache, const ScLookupCache::QueryCriteria& rCriteria, const ScQueryParam & rParam)
{ // is lookup value empty? const ScQueryEntry& rEntry = rParam.GetEntry(0); const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); if (! rItem.maString.getString().isEmpty()) return -1; // not found
// try to find the row index for which we have already performed lookup // and have some result of it inside cache return rCache.lookup( rCriteria );
}
bool ScInterpreter::LookupQueryWithCache( ScAddress & o_rResultPos, const ScQueryParam & rParam, const ScComplexRefData* refData,
LookupSearchMode nSearchMode, sal_uInt16 nOpCode ) const
{ bool bFound = false; const ScQueryEntry& rEntry = rParam.GetEntry(0); bool bColumnsMatch = (rParam.nCol1 == rEntry.nField); // At least all volatile functions that generate indirect references have // to force non-cached lookup. /* TODO: We could further classify volatile functions into reference *generatingandnotreferencegeneratingfunctionstohavetoforceless *directlookupshere.Wecouldevenfurtherattributevolatilityper
* parameter so it would affect only the lookup range parameter. */ if (!bColumnsMatch || GetVolatileType() != NOT_VOLATILE)
bFound = lcl_LookupQuery( o_rResultPos, mrDoc, mrContext, rParam, rEntry, pMyFormulaCell,
refData, nSearchMode, nOpCode ); else
{
ScRange aLookupRange( rParam.nCol1, rParam.nRow1, rParam.nTab,
rParam.nCol2, rParam.nRow2, rParam.nTab);
ScLookupCache& rCache = mrDoc.GetLookupCache( aLookupRange, &mrContext );
ScLookupCache::QueryCriteria aCriteria( rEntry, nSearchMode);
ScLookupCache::Result eCacheResult = rCache.lookup( o_rResultPos,
aCriteria, aPos);
// tdf#121052: Slow load of cells with VLOOKUP with references to empty cells // This check was added only for speed up to avoid several useless complete // lookups inside [RangeArray] for searching empty strings. if (eCacheResult == ScLookupCache::NOT_CACHED && aCriteria.isEmptyStringQuery())
{ const SCROW nPrevRowWithEmptyValueLookup = lcl_getPrevRowWithEmptyValueLookup(rCache, aCriteria, rParam); if (nPrevRowWithEmptyValueLookup >= 0)
{ // make the same lookup using cache with different row index // (this lookup was already cached)
ScAddress aPosPrev(aPos);
aPosPrev.SetRow(nPrevRowWithEmptyValueLookup);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.