tdf#127293 Add Excel2021 function XLOOKUP to Calc

https://issues.oasis-open.org/browse/OFFICE-4154

What is working already: xlookup with normal forward,
backward search in columns/rows. Binary search in rows with
real binary search algorithm, in columns only works with linear search yet.
Linear forward backward wildcard/regex search in columns/rows.
Looking for the first smaller or greater value with linear and binary search
ALso all the combination of all these options. Except XLOOKUP
not supperted wildcard/regex search with binary search.

TODO in next patches:
- add the binary search option for searching in columns.
- Evaluate Formula calculation not working in general.

Co-authored-by: Balazs Varga <balazs.varga.extern@allotropia.de>

Change-Id: I15fd4479b63ec13b093d269760d1bbb5957553e8
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131905
Tested-by: Jenkins
Tested-by: Gabor Kelemen <gabor.kelemen.extern@allotropia.de>
Reviewed-by: Balazs Varga <balazs.varga.extern@allotropia.de>
This commit is contained in:
Winfried Donkers 2023-06-28 16:13:45 +02:00 committed by Balazs Varga
parent 8cccfc82a3
commit f7039822c7
20 changed files with 5759 additions and 286 deletions

View File

@ -275,6 +275,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_ODFF[] =
{ "COUNTIFS" , SC_OPCODE_COUNT_IFS },
{ "LOOKUP" , SC_OPCODE_LOOKUP },
{ "VLOOKUP" , SC_OPCODE_V_LOOKUP },
{ "COM.MICROSOFT.XLOOKUP" , SC_OPCODE_X_LOOKUP },
{ "HLOOKUP" , SC_OPCODE_H_LOOKUP },
{ "ORG.OPENOFFICE.MULTIRANGE" , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ "OFFSET" , SC_OPCODE_OFFSET },
@ -722,6 +723,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_OOXML[] =
{ "COUNTIFS" , SC_OPCODE_COUNT_IFS },
{ "LOOKUP" , SC_OPCODE_LOOKUP },
{ "VLOOKUP" , SC_OPCODE_V_LOOKUP },
{ "_xlfn.XLOOKUP" , SC_OPCODE_X_LOOKUP },
{ "HLOOKUP" , SC_OPCODE_H_LOOKUP },
{ "_xlfn.ORG.OPENOFFICE.MULTIRANGE" , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ "OFFSET" , SC_OPCODE_OFFSET },
@ -1172,6 +1174,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_PODF[] =
{ "COUNTIFS" , SC_OPCODE_COUNT_IFS },
{ "LOOKUP" , SC_OPCODE_LOOKUP },
{ "VLOOKUP" , SC_OPCODE_V_LOOKUP },
{ "XLOOKUP" , SC_OPCODE_X_LOOKUP },
{ "HLOOKUP" , SC_OPCODE_H_LOOKUP },
{ "MULTIRANGE" , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ "OFFSET" , SC_OPCODE_OFFSET },
@ -1623,6 +1626,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_API[] =
{ "COUNTIFS" , SC_OPCODE_COUNT_IFS },
{ "LOOKUP" , SC_OPCODE_LOOKUP },
{ "VLOOKUP" , SC_OPCODE_V_LOOKUP },
{ "XLOOKUP" , SC_OPCODE_X_LOOKUP },
{ "HLOOKUP" , SC_OPCODE_H_LOOKUP },
{ "MULTIRANGE" , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ "OFFSET" , SC_OPCODE_OFFSET },
@ -2072,6 +2076,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH[] =
{ "COUNTIFS" , SC_OPCODE_COUNT_IFS },
{ "LOOKUP" , SC_OPCODE_LOOKUP },
{ "VLOOKUP" , SC_OPCODE_V_LOOKUP },
{ "XLOOKUP" , SC_OPCODE_X_LOOKUP },
{ "HLOOKUP" , SC_OPCODE_H_LOOKUP },
{ "MULTIRANGE" , SC_OPCODE_MULTI_AREA },
{ "OFFSET" , SC_OPCODE_OFFSET },
@ -2502,6 +2507,7 @@ const std::pair<TranslateId, int> RID_STRLIST_FUNCTION_NAMES[] =
{ NC_("RID_STRLIST_FUNCTION_NAMES", "COUNTIFS") , SC_OPCODE_COUNT_IFS },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "LOOKUP") , SC_OPCODE_LOOKUP },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "VLOOKUP") , SC_OPCODE_V_LOOKUP },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "XLOOKUP") , SC_OPCODE_X_LOOKUP },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "HLOOKUP") , SC_OPCODE_H_LOOKUP },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "MULTIRANGE") , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ NC_("RID_STRLIST_FUNCTION_NAMES", "OFFSET") , SC_OPCODE_OFFSET },

View File

@ -398,7 +398,7 @@
#define SC_OPCODE_CELL 385
#define SC_OPCODE_ISPMT 386
#define SC_OPCODE_HYPERLINK 387
// free: 388
#define SC_OPCODE_X_LOOKUP 388
// free: 389
#define SC_OPCODE_GET_PIVOT_DATA 390
#define SC_OPCODE_EUROCONVERT 391

View File

@ -316,6 +316,7 @@ enum OpCode : sal_uInt16
ocCountIfs = SC_OPCODE_COUNT_IFS,
ocLookup = SC_OPCODE_LOOKUP,
ocVLookup = SC_OPCODE_V_LOOKUP,
ocXLookup = SC_OPCODE_X_LOOKUP,
ocHLookup = SC_OPCODE_H_LOOKUP,
ocMultiArea = SC_OPCODE_MULTI_AREA,
ocOffset = SC_OPCODE_OFFSET,
@ -798,6 +799,7 @@ inline std::string OpCodeEnumToString(OpCode eCode)
case ocCountIfs: return "CountIfs";
case ocLookup: return "Lookup";
case ocVLookup: return "VLookup";
case ocXLookup: return "XLookup";
case ocHLookup: return "HLookup";
case ocMultiArea: return "MultiArea";
case ocOffset: return "Offset";

View File

@ -75,6 +75,7 @@ https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocume
* LOOKUP
* MATCH
* VLOOKUP
* XLOOKUP
* Mathematical Functions
* SUMIF
* SUMIFS

View File

@ -593,5 +593,6 @@ inline constexpr OUString HID_FUNC_REGEX = u"SC_HID_FUNC_REGEX"_ustr;
inline constexpr OUString HID_FUNC_FOURIER = u"SC_HID_FUNC_FOURIER"_ustr;
inline constexpr OUString HID_FUNC_RAND_NV = u"SC_HID_FUNC_RAND_NV"_ustr;
inline constexpr OUString HID_FUNC_RANDBETWEEN_NV = u"SC_HID_FUNC_RANDBETWEEN_NV"_ustr;
inline constexpr OUString HID_FUNC_XLOOKUP_MS = u"SC_HID_FUNC_XLOOKUP_MS"_ustr;
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -62,7 +62,7 @@ class ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >
{
protected:
ScQueryCellIteratorAccessSpecific( ScDocument& rDocument, ScInterpreterContext& rContext,
const ScQueryParam& rParam );
const ScQueryParam& rParam, bool bReverseSearch );
// Initialize position for new column.
void InitPos();
// Increase position (next row).
@ -71,12 +71,19 @@ protected:
// should call IncPos().
void IncBlock();
// Decrease position (prev row).
void DecPos();
// Prev mdds block. If access is not direct/linear, then
// should call DecPos().
void DecBlock();
// These members needs to be available already in the base class.
typedef sc::CellStoreType::const_position_type PositionType;
PositionType maCurPos;
ScQueryParam maParam;
ScDocument& rDoc;
ScInterpreterContext& mrContext;
bool mbReverseSearch;
SCTAB nTab;
SCCOL nCol;
SCROW nRow;
@ -98,19 +105,24 @@ public:
bool IncPosImpl();
protected:
ScQueryCellIteratorAccessSpecific( ScDocument& rDocument, ScInterpreterContext& rContext,
const ScQueryParam& rParam );
const ScQueryParam& rParam, bool bReverseSearch );
void InitPosStart();
void InitPosFinish( SCROW beforeRow, SCROW lastRow );
void InitPosFinish( SCROW beforeRow, SCROW lastRow, bool bFirstMatch );
void IncPos() { IncPosImpl<false>(); }
bool IncPosFast() { return IncPosImpl<true>(); }
void IncBlock() { IncPos(); } // Cannot skip entire block, not linear.
// Initialize for backward search. (no need for SortedCache)
static void DecPos() {};
static void DecBlock() {};
// These members needs to be available already in the base class.
typedef sc::CellStoreType::const_position_type PositionType;
PositionType maCurPos;
ScQueryParam maParam;
ScDocument& rDoc;
ScInterpreterContext& mrContext;
bool mbReverseSearch;
SCTAB nTab;
SCCOL nCol;
SCROW nRow;
@ -163,17 +175,24 @@ protected:
sal_uInt8 nTestEqualCondition;
bool bAdvanceQuery;
bool bIgnoreMismatchOnLeadingStrings;
bool bSortedBinarySearch;
bool bXLookUp;
SCCOL nBestFitCol;
SCROW nBestFitRow;
// Make base members directly visible here (templated bases need 'this->').
using AccessBase::maCurPos;
using AccessBase::maParam;
using AccessBase::rDoc;
using AccessBase::mrContext;
using AccessBase::mbReverseSearch;
using AccessBase::nTab;
using AccessBase::nCol;
using AccessBase::nRow;
using AccessBase::IncPos;
using AccessBase::IncBlock;
using AccessBase::DecPos;
using AccessBase::DecBlock;
using typename AccessBase::BinarySearchCellType;
using AccessBase::MakeBinarySearchIndexer;
using TypeBase::HandleItemFound;
@ -227,9 +246,14 @@ protected:
bool IsEqualConditionFulfilled() const
{ return nTestEqualCondition == nTestEqualConditionFulfilled; }
void HandleBestFitItemFound(SCCOL nBFitCol, SCROW nBFitRow)
{
nBestFitCol = nBFitCol;
nBestFitRow = nBFitRow;
}
public:
ScQueryCellIteratorBase(ScDocument& rDocument, ScInterpreterContext& rContext, SCTAB nTable,
const ScQueryParam& aParam, bool bMod);
const ScQueryParam& aParam, bool bMod, bool bReverse);
// when !bMod, the QueryParam has to be filled
// (bIsString)
@ -238,6 +262,12 @@ public:
void SetAdvanceQueryParamEntryField( bool bVal )
{ bAdvanceQuery = bVal; }
void AdvanceQueryParamEntryField();
void SetSortedBinarySearchMode( bool bVal )
{ bSortedBinarySearch = bVal; }
void SetXlookupMode( bool bVal )
{ bXLookUp = bVal; }
};
@ -259,11 +289,13 @@ class ScQueryCellIterator
using Base::maParam;
using Base::rDoc;
using Base::mrContext;
using Base::mbReverseSearch;
using Base::nTab;
using Base::nCol;
using Base::nRow;
using Base::InitPos;
using Base::IncPos;
using Base::DecPos;
using Base::bIgnoreMismatchOnLeadingStrings;
using Base::SetStopOnMismatch;
using Base::SetTestEqualCondition;
@ -279,13 +311,17 @@ class ScQueryCellIterator
using Base::nTestEqualConditionEnabled;
using Base::PerformQuery;
using Base::getThisResult;
using Base::nBestFitCol;
using Base::nBestFitRow;
using Base::bSortedBinarySearch;
using Base::bXLookUp;
bool GetThis();
public:
ScQueryCellIterator(ScDocument& rDocument, ScInterpreterContext& rContext, SCTAB nTable,
const ScQueryParam& aParam, bool bMod)
: Base( rDocument, rContext, nTable, aParam, bMod ) {}
const ScQueryParam& aParam, bool bMod, bool bReverse)
: Base( rDocument, rContext, nTable, aParam, bMod, bReverse ) {}
bool GetFirst();
bool GetNext();
SCCOL GetCol() const { return nCol; }
@ -320,8 +356,8 @@ class ScQueryCellIteratorSortedCache
typedef ScQueryCellIterator< ScQueryCellIteratorAccess::SortedCache > Base;
public:
ScQueryCellIteratorSortedCache(ScDocument& rDocument, ScInterpreterContext& rContext,
SCTAB nTable, const ScQueryParam& aParam, bool bMod)
: Base( rDocument, rContext, nTable, aParam, bMod ) {}
SCTAB nTable, const ScQueryParam& aParam, bool bMod, bool bReverse )
: Base( rDocument, rContext, nTable, aParam, bMod, bReverse ) {}
// Returns true if this iterator can be used for the given query.
static bool CanBeUsed(ScDocument& rDoc, const ScQueryParam& aParam,
SCTAB nTab, const ScFormulaCell* cell, const ScComplexRefData* refData,
@ -357,8 +393,8 @@ protected:
public:
ScCountIfCellIterator(ScDocument& rDocument, ScInterpreterContext& rContext, SCTAB nTable,
const ScQueryParam& aParam, bool bMod)
: Base( rDocument, rContext, nTable, aParam, bMod ) {}
const ScQueryParam& aParam, bool bMod, bool bReverse)
: Base( rDocument, rContext, nTable, aParam, bMod, bReverse ) {}
sal_uInt64 GetCount();
};
@ -370,8 +406,8 @@ class ScCountIfCellIteratorSortedCache
typedef ScCountIfCellIterator< ScQueryCellIteratorAccess::SortedCache > Base;
public:
ScCountIfCellIteratorSortedCache(ScDocument& rDocument, ScInterpreterContext& rContext,
SCTAB nTable, const ScQueryParam& aParam, bool bMod)
: Base( rDocument, rContext, nTable, aParam, bMod ) {}
SCTAB nTable, const ScQueryParam& aParam, bool bMod, bool bReverse)
: Base( rDocument, rContext, nTable, aParam, bMod, bReverse ) {}
// Returns true if this iterator can be used for the given query.
static bool CanBeUsed(ScDocument& rDoc, const ScQueryParam& aParam,
SCTAB nTab, const ScFormulaCell* cell, const ScComplexRefData* refData,

View File

@ -3377,6 +3377,24 @@ const TranslateId SC_OPCODE_V_LOOKUP_ARY[] =
NC_("SC_OPCODE_V_LOOKUP", "If the value is TRUE or not given, the search column of the array represents a series of ranges, and must be sorted in ascending order.")
};
// -=*# Resource for function XLOOKUP #*=-
const TranslateId SC_OPCODE_X_LOOKUP_ARY[] =
{
NC_("SC_OPCODE_X_LOOKUP", "Extended vertical search and reference to indicated cells."),
NC_("SC_OPCODE_X_LOOKUP", "Search criterion"),
NC_("SC_OPCODE_X_LOOKUP", "The value to be found in the first column."),
NC_("SC_OPCODE_X_LOOKUP", "Search Array"),
NC_("SC_OPCODE_X_LOOKUP", "The array or range to search."),
NC_("SC_OPCODE_X_LOOKUP", "Result Array"),
NC_("SC_OPCODE_X_LOOKUP", "The array or range to return."),
NC_("SC_OPCODE_X_LOOKUP", "Result if not found"),
NC_("SC_OPCODE_X_LOOKUP", "If given, return given text, otherwise return #N/A."),
NC_("SC_OPCODE_X_LOOKUP", "Match Mode"),
NC_("SC_OPCODE_X_LOOKUP", "0, -1, 1 or 2 "), // TODO : add explanation of values
NC_("SC_OPCODE_X_LOOKUP", "Search Mode"),
NC_("SC_OPCODE_X_LOOKUP", "1, -1, 2 or -2 ") // TODO : add explanation of values
};
// -=*# Resource for function INDEX #*=-
const TranslateId SC_OPCODE_INDEX_ARY[] =
{

View File

@ -78,7 +78,7 @@ public:
ScFunctionListObj::ScFunctionListObj()
: UnoApiTest("/sc/qa/extras/testdocuments")
, XElementAccess(cppu::UnoType<uno::Sequence<beans::PropertyValue>>::get())
, XIndexAccess(395)
, XIndexAccess(396)
, XNameAccess("IF")
, XServiceInfo("stardiv.StarCalc.ScFunctionListObj", "com.sun.star.sheet.FunctionDescriptions")
{

File diff suppressed because one or more lines are too long

View File

@ -2922,6 +2922,7 @@ CPPUNIT_TEST_FIXTURE(Test, testFunctionLists)
"SHEETS",
"STYLE",
"VLOOKUP",
"XLOOKUP",
nullptr
};

View File

@ -1972,8 +1972,8 @@ class TestQueryIterator
typedef ScQueryCellIteratorBase< ScQueryCellIteratorAccess::Direct, ScQueryCellIteratorType::Generic > Base;
public:
TestQueryIterator( ScDocument& rDocument, ScInterpreterContext& rContext, SCTAB nTable,
const ScQueryParam& aParam, bool bMod )
: Base( rDocument, rContext, nTable, aParam, bMod )
const ScQueryParam& aParam, bool bMod, bool bReverse = false )
: Base( rDocument, rContext, nTable, aParam, bMod, bReverse )
{
}
using Base::BinarySearch; // make public

View File

@ -675,6 +675,7 @@ ScFunctionList::ScFunctionList( bool bEnglishFunctionNames )
{ SC_OPCODE_CELL, ENTRY(SC_OPCODE_CELL_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ZELLE, 2, { 0, 1 }, 0 },
{ SC_OPCODE_ISPMT, ENTRY(SC_OPCODE_ISPMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ISPMT, 4, { 0, 0, 0, 0 }, 0 },
{ SC_OPCODE_HYPERLINK, ENTRY(SC_OPCODE_HYPERLINK_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_HYPERLINK, 2, { 0, 1 }, 0 },
{ SC_OPCODE_X_LOOKUP, ENTRY(SC_OPCODE_X_LOOKUP_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_XLOOKUP_MS, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
{ SC_OPCODE_GET_PIVOT_DATA, ENTRY(SC_OPCODE_GET_PIVOT_DATA_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_GETPIVOTDATA, VAR_ARGS+2, { 0, 0, 1 }, 0 },
{ SC_OPCODE_EUROCONVERT, ENTRY(SC_OPCODE_EUROCONVERT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_EUROCONVERT, 5, { 0, 0, 0, 1, 1 }, 0 },
{ SC_OPCODE_NUMBERVALUE, ENTRY(SC_OPCODE_NUMBERVALUE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_NUMBERVALUE, 3, { 0, 1, 1 }, 0 },

View File

@ -60,16 +60,20 @@
template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
ScQueryCellIteratorBase< accessType, queryType >::ScQueryCellIteratorBase(ScDocument& rDocument,
ScInterpreterContext& rContext, SCTAB nTable, const ScQueryParam& rParam, bool bMod )
: AccessBase( rDocument, rContext, rParam )
ScInterpreterContext& rContext, SCTAB nTable, const ScQueryParam& rParam, bool bMod, bool bReverse )
: AccessBase( rDocument, rContext, rParam, bReverse )
, nStopOnMismatch( nStopOnMismatchDisabled )
, nTestEqualCondition( nTestEqualConditionDisabled )
, bAdvanceQuery( false )
, bIgnoreMismatchOnLeadingStrings( false )
, bSortedBinarySearch( false )
, bXLookUp( false )
, nBestFitCol(SCCOL_MAX)
, nBestFitRow(SCROW_MAX)
{
nTab = nTable;
nCol = maParam.nCol1;
nRow = maParam.nRow1;
nCol = !bReverse ? maParam.nCol1 : maParam.nCol2;
nRow = !bReverse ? maParam.nRow1 : maParam.nRow2;
SCSIZE i;
if (!bMod) // Or else it's already inserted
return;
@ -127,7 +131,7 @@ void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
bool bNextColumn = maCurPos.first == pCol->maCells.end();
if (!bNextColumn)
{
if (nRow > maParam.nRow2)
if ((!mbReverseSearch && nRow > maParam.nRow2) || (mbReverseSearch && nRow < maParam.nRow1))
bNextColumn = true;
}
@ -135,9 +139,18 @@ void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
{
do
{
++nCol;
if (nCol > maParam.nCol2 || nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount())
return;
if (!mbReverseSearch)
{
++nCol;
if (nCol > maParam.nCol2 || nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount())
return;
}
else
{
--nCol;
if (nCol < maParam.nCol1 || nCol < static_cast<SCCOL>(0))
return;
}
if ( bAdvanceQuery )
{
AdvanceQueryParamEntryField();
@ -169,12 +182,12 @@ void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
// ValidQuery().
if(HandleItemFound())
return;
IncPos();
!mbReverseSearch ? IncPos() : DecPos();
continue;
}
else
{
IncBlock();
!mbReverseSearch ? IncBlock() : DecBlock();
continue;
}
}
@ -182,7 +195,7 @@ void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
ScRefCellValue aCell = sc::toRefCell(maCurPos.first, maCurPos.second);
if (bAllStringIgnore && aCell.hasString())
IncPos();
!mbReverseSearch ? IncPos() : DecPos();
else
{
if ( queryEvaluator.ValidQuery( nRow,
@ -192,9 +205,54 @@ void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
nTestEqualCondition |= nTestEqualConditionMatched;
if ( aCell.isEmpty())
return;
if( HandleItemFound())
// XLookUp: Forward/backward search for best fit value, except if we have an exact match
if (bXLookUp && !bSortedBinarySearch && (rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL) &&
(nBestFitCol != nCol || nBestFitRow != nRow))
{
bool bNumSearch = rItem.meType == ScQueryEntry::ByValue && aCell.hasNumeric();
bool bStringSearch = rItem.meType == ScQueryEntry::ByString && aCell.hasString();
if (bNumSearch || bStringSearch)
{
if (nTestEqualCondition == nTestEqualConditionFulfilled || (nBestFitCol == SCCOL_MAX && nBestFitRow == SCROW_MAX))
HandleBestFitItemFound(nCol, nRow);
else
{
ScAddress aBFAddr(nBestFitCol, nBestFitRow, nTab);
ScRefCellValue aBFCell(rDoc, aBFAddr);
ScQueryParam aParamTmp(maParam);
ScQueryEntry& rEntryTmp = aParamTmp.GetEntry(0);
if (rEntry.eOp == SC_LESS_EQUAL)
rEntryTmp.eOp = SC_GREATER;
else if (rEntry.eOp == SC_GREATER_EQUAL)
rEntryTmp.eOp = SC_LESS;
ScQueryEntry::Item& rItemTmp = rEntryTmp.GetQueryItem();
if (bNumSearch)
rItemTmp.mfVal = aBFCell.getValue();
else if (bStringSearch)
rItemTmp.maString = svl::SharedString(aBFCell.getString(&rDoc));
ScQueryEvaluator queryEvaluatorTmp(rDoc, *rDoc.maTabs[nTab], aParamTmp, &mrContext, nullptr);
if (queryEvaluatorTmp.ValidQuery(nRow, (nCol == static_cast<SCCOL>(nFirstQueryField) ? &aCell : nullptr)))
HandleBestFitItemFound(nCol, nRow);
else
{
!mbReverseSearch ? IncPos() : DecPos();
continue;
}
}
}
else
{
!mbReverseSearch ? IncPos() : DecPos();
continue;
}
}
if (HandleItemFound())
return;
IncPos();
!mbReverseSearch ? IncPos() : DecPos();
continue;
}
else if ( nStopOnMismatch )
@ -213,7 +271,7 @@ void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
{
if (aCell.hasString())
{
IncPos();
!mbReverseSearch ? IncPos() : DecPos();
bStop = false;
}
else
@ -228,7 +286,7 @@ void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
}
}
else
IncPos();
!mbReverseSearch ? IncPos() : DecPos();
}
bFirstStringIgnore = false;
}
@ -279,7 +337,8 @@ void ScQueryCellIteratorBase< accessType, queryType >::InitPos()
if( BinarySearch( nCol ))
lastRow = nRow;
}
AccessBase::InitPosFinish( beforeRow, lastRow );
bool bFirstMatch = (bXLookUp && op != SC_EQUAL);
AccessBase::InitPosFinish(beforeRow, lastRow, bFirstMatch);
}
}
@ -292,11 +351,13 @@ void ScQueryCellIteratorBase< accessType, queryType >::AdvanceQueryParamEntryFie
ScQueryEntry& rEntry = maParam.GetEntry( j );
if ( rEntry.bDoQuery )
{
if ( rEntry.nField < rDoc.MaxCol() )
if (!mbReverseSearch && rEntry.nField < rDoc.MaxCol())
rEntry.nField++;
else if (mbReverseSearch && rEntry.nField > static_cast<SCCOLROW>(0))
rEntry.nField--;
else
{
assert(!"AdvanceQueryParamEntryField: ++rEntry.nField > MAXCOL");
assert(!"AdvanceQueryParamEntryField: ++rEntry.nField > MAXCOL || --rEntry.nField < 0");
}
}
else
@ -670,14 +731,25 @@ bool ScQueryCellIterator< accessType >::FindEqualOrSortedLastInRange( SCCOL& nFo
nFoundCol = rDoc.MaxCol()+1;
nFoundRow = rDoc.MaxRow()+1;
SetStopOnMismatch( true ); // assume sorted keys
if (bXLookUp && !bSortedBinarySearch)
SetStopOnMismatch( false ); // assume not sorted keys for XLookup
else
SetStopOnMismatch( true ); // assume sorted keys
SetTestEqualCondition( true );
bIgnoreMismatchOnLeadingStrings = true;
bool bLiteral = maParam.eSearchType == utl::SearchParam::SearchType::Normal &&
maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString;
bool bBinary = maParam.bByRow &&
(bLiteral || maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue) &&
(maParam.GetEntry(0).eOp == SC_LESS_EQUAL || maParam.GetEntry(0).eOp == SC_GREATER_EQUAL);
// assume not sorted properly if we are using XLookup with forward or backward search
if (bBinary && bXLookUp && !bSortedBinarySearch)
bBinary = false;
bool bFound = false;
if (bBinary)
{
@ -768,9 +840,10 @@ bool ScQueryCellIterator< accessType >::FindEqualOrSortedLastInRange( SCCOL& nFo
}
}
}
if ( IsEqualConditionFulfilled() )
if ( IsEqualConditionFulfilled() && !bXLookUp )
{
// Position on last equal entry.
// Position on last equal entry, except for XLOOKUP,
// which looking for the first equal entry
SCSIZE nEntries = maParam.GetEntryCount();
for ( SCSIZE j = 0; j < nEntries; j++ )
{
@ -861,19 +934,27 @@ bool ScQueryCellIterator< accessType >::FindEqualOrSortedLastInRange( SCCOL& nFo
ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >
::ScQueryCellIteratorAccessSpecific( ScDocument& rDocument,
ScInterpreterContext& rContext, const ScQueryParam& rParam )
ScInterpreterContext& rContext, const ScQueryParam& rParam, bool bReverseSearch )
: maParam( rParam )
, rDoc( rDocument )
, mrContext( rContext )
, mbReverseSearch( bReverseSearch )
{
// coverity[uninit_member] - this just contains data, subclass will initialize some of it
}
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::InitPos()
{
nRow = maParam.nRow1;
if (maParam.bHasHeader && maParam.bByRow)
++nRow;
if (!mbReverseSearch)
{
nRow = maParam.nRow1;
if (maParam.bHasHeader && maParam.bByRow)
++nRow;
}
else
{
nRow = maParam.nRow2;
}
const ScColumn& rCol = rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
maCurPos = rCol.maCells.position(nRow);
}
@ -891,6 +972,19 @@ void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::Inc
IncBlock();
}
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::DecPos()
{
if (maCurPos.second > 0)
{
// Move within the same block.
--maCurPos.second;
--nRow;
}
else
// Move to the prev block.
DecBlock();
}
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::IncBlock()
{
++maCurPos.first;
@ -899,6 +993,26 @@ void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::Inc
nRow = maCurPos.first->position;
}
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::DecBlock()
{
// Set current position to the last possible row.
const ScColumn& rCol = rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
if (maCurPos.first != rCol.maCells.begin())
{
--maCurPos.first;
maCurPos.second = maCurPos.first->size - 1;
nRow = maCurPos.first->position + maCurPos.second;
}
else
{
// No rows, set to end. This will make PerformQuery() go to next column.
nRow = maParam.nRow1 - 1;
maCurPos.first = rCol.maCells.end();
maCurPos.second = 0;
}
}
/**
* This class sequentially indexes non-empty cells in order, from the top of
* the block where the start row position is, to the bottom of the block
@ -1073,10 +1187,11 @@ ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::MakeBina
ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >
::ScQueryCellIteratorAccessSpecific( ScDocument& rDocument,
ScInterpreterContext& rContext, const ScQueryParam& rParam )
ScInterpreterContext& rContext, const ScQueryParam& rParam, bool bReverseSearch )
: maParam( rParam )
, rDoc( rDocument )
, mrContext( rContext )
, mbReverseSearch( bReverseSearch )
{
// coverity[uninit_member] - this just contains data, subclass will initialize some of it
}
@ -1101,7 +1216,7 @@ void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >
}
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::InitPosFinish(
SCROW beforeRow, SCROW lastRow )
SCROW beforeRow, SCROW lastRow, bool bFirstMatch )
{
pColumn = &rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
if(lastRow >= 0)
@ -1110,7 +1225,10 @@ void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >
sortedCachePosLast = sortedCache->indexForRow(lastRow);
if(sortedCachePos <= sortedCachePosLast)
{
nRow = sortedCache->rowForIndex(sortedCachePos);
if (!bFirstMatch)
nRow = sortedCache->rowForIndex(sortedCachePos);
else
nRow = sortedCache->rowForIndex(sortedCachePosLast);
maCurPos = pColumn->maCells.position(nRow);
return;
}
@ -1322,7 +1440,10 @@ template< ScQueryCellIteratorAccess accessType >
bool ScQueryCellIterator< accessType >::GetFirst()
{
assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
nCol = maParam.nCol1;
if (!mbReverseSearch)
nCol = maParam.nCol1;
else
nCol = maParam.nCol2;
InitPos();
return GetThis();
}
@ -1330,7 +1451,10 @@ bool ScQueryCellIterator< accessType >::GetFirst()
template< ScQueryCellIteratorAccess accessType >
bool ScQueryCellIterator< accessType >::GetNext()
{
IncPos();
if (!mbReverseSearch)
IncPos();
else
DecPos();
if ( nStopOnMismatch )
nStopOnMismatch = nStopOnMismatchEnabled;
if ( nTestEqualCondition )

View File

@ -30,6 +30,7 @@
#include <token.hxx>
#include <math.hxx>
#include <kahan.hxx>
#include <queryiter.hxx>
#include "parclass.hxx"
#include <map>
@ -55,6 +56,57 @@ struct ScInterpreterContext;
class ScJumpMatrix;
struct ScRefCellValue;
enum MatchMode{ exactorNA=0, exactorS=-1, exactorG=1, wildcard=2 };
enum SearchMode{ searchfwd=1, searchrev=-1, searchbasc=2, searchbdesc=-2 };
struct VectorSearchArguments
{
// struct contains the contents of the function arguments
// Struct owner, ScMatch or ScXLookup
bool isXLookup = false;
// match mode (common, enum values are from XLOOKUP)
// optional 5th argument to set match mode
// 0 - Exact match. If none found, return #N/A. (MATCH value 0)
// -1 - Exact match. If none found, return the next smaller item. (MATCH value 1)
// 1 - Exact match. If none found, return the next larger item. (MATCH value -1)
// 2 - A wildcard match where *, ?, and ~ have special meaning. (XLOOKUP only)
// TODO : is this enum needed, or do we solely use rEntry.eOp ?
MatchMode eMatchMode = exactorG;
// value to be searched for (common)
SCCOL nCol1 = 0;
SCROW nRow1 = 0;
SCTAB nTab1 = 0;
SCCOL nCol2 = 0;
SCROW nRow2 = 0;
SCTAB nTab2 = 0;
ScMatrixRef pMatSrc;
bool isStringSearch = true;
double fSearchVal;
svl::SharedString sSearchStr;
bool bVLookup;
// search mode (only XLOOKUP has all 4 options, MATCH only uses searchfwd)
// optional 6th argument to set search mode
// 1 - Perform a search starting at the first item. This is the default.
// -1 - Perform a reverse search starting at the last item.
// 2 - Perform a binary search that relies on lookup_array being sorted in ascending order.
// If not sorted, invalid results will be returned.
// -2 - Perform a binary search that relies on lookup_array being sorted in descending order.
// If not sorted, invalid results will be returned.
//
SearchMode eSearchMode = searchfwd;
// search variables
SCSIZE nHitIndex = 0;
SCSIZE nBestFit = SCSIZE_MAX;
// result
int nIndex = -1;
bool isResultNA = false;
};
namespace sc {
struct CompareOptions;
@ -234,6 +286,10 @@ private:
bool IsTableOpInRange( const ScRange& );
sal_uInt32 GetCellNumberFormat( const ScAddress& rPos, ScRefCellValue& rCell );
double ConvertStringToValue( const OUString& );
bool SearchVectorForValue( VectorSearchArguments& );
bool SearchMatrixForValue( VectorSearchArguments&, ScQueryParam&, ScQueryEntry&, ScQueryEntry::Item& );
bool SearchRangeForValue( VectorSearchArguments&, ScQueryParam&, ScQueryEntry& );
public:
static double ScGetGCD(double fx, double fy);
/** For matrix back calls into the current interpreter.
@ -485,8 +541,8 @@ private:
// Set error according to rVal, and set rVal to 0.0 if there was an error.
inline void TreatDoubleError( double& rVal );
// Lookup using ScLookupCache, @returns true if found and result address
bool LookupQueryWithCache( ScAddress & o_rResultPos,
const ScQueryParam & rParam, const ScComplexRefData* refData ) const;
bool LookupQueryWithCache( ScAddress & o_rResultPos, const ScQueryParam & rParam,
const ScComplexRefData* refData, sal_Int8 nSearchMode, bool bXlookupMode ) const;
void ScIfJump();
void ScIfError( bool bNAonly );
@ -628,6 +684,7 @@ private:
void ScLookup();
void ScHLookup();
void ScVLookup();
void ScXLookup();
void ScSubTotal();
// If upon call rMissingField==true then the database field parameter may be

File diff suppressed because it is too large Load Diff

View File

@ -4326,6 +4326,7 @@ StackVar ScInterpreter::Interpret()
case ocCountIfs : ScCountIfs(); break;
case ocLookup : ScLookup(); break;
case ocVLookup : ScVLookup(); break;
case ocXLookup : ScXLookup(); break;
case ocHLookup : ScHLookup(); break;
case ocIndex : ScIndex(); break;
case ocMultiArea : ScMultiArea(); break;

View File

@ -264,6 +264,7 @@ const ScParameterClassification::RawData ScParameterClassification::pRawData[] =
{ ocVarS, {{ Reference }, 1, Value }},
{ ocWhitespace, {{ Bounds }, 0, Bounds }},
{ ocWorkday_MS, {{ Value, Value, Value, Reference }, 0, Value }},
{ ocXLookup, {{ Value, ReferenceOrForceArray, ReferenceOrForceArray, Value, Value, Value }, 0, Value }},
{ ocXor, {{ Reference }, 1, Value }},
{ ocZTest, {{ Reference, Value, Value }, 0, Value }},
{ ocZTest_MS, {{ Reference, Value, Value }, 0, Value }},

View File

@ -1387,6 +1387,7 @@ void ScTokenArray::CheckToken( const FormulaToken& r )
case ocCount:
case ocCount2:
case ocVLookup:
case ocXLookup:
case ocSLN:
case ocIRR:
case ocMIRR:

View File

@ -593,6 +593,18 @@ const XclFunctionInfo saFuncTable_2016[] =
EXC_FUNCENTRY_V_VR( ocMaxIfs_MS, 3, MX, 0, "MAXIFS" )
};
/** Functions new in Excel 2021.
@See sc/source/filter/oox/formulabase.cxx saFuncTable2021 for V,VR,RO,...
*/
const XclFunctionInfo saFuncTable_2021[] =
{
EXC_FUNCENTRY_V_VR( ocXLookup, 3, 6, 0, "XLOOKUP" )
};
#define EXC_FUNCENTRY_ODF( opcode, minparam, maxparam, flags, asciiname ) \
{ opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME_ODF( asciiname ) }, \
{ opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME_ODF( asciiname ) }
@ -669,6 +681,7 @@ XclFunctionProvider::XclFunctionProvider( const XclRoot& rRoot )
(this->*pFillFunc)(saFuncTable_2010, std::end(saFuncTable_2010));
(this->*pFillFunc)(saFuncTable_2013, std::end(saFuncTable_2013));
(this->*pFillFunc)(saFuncTable_2016, std::end(saFuncTable_2016));
(this->*pFillFunc)(saFuncTable_2021, std::end(saFuncTable_2021));
(this->*pFillFunc)(saFuncTable_Odf, std::end(saFuncTable_Odf));
(this->*pFillFunc)(saFuncTable_OOoLO, std::end(saFuncTable_OOoLO));
}

View File

@ -866,6 +866,20 @@ const FunctionData saFuncTable2016[] =
};
/** Functions new in Excel 2021.
@See sc/source/filter/excel/xlformula.cxx saFuncTable_2021
*/
/* FIXME: BIFF?? function identifiers available? Where to obtain? */
const FunctionData saFuncTable2021[] =
{
{ "COM.MICROSOFT.XLOOKUP", "XLOOKUP", NOID, NOID, 3, 6, V, { VR, VA, VR }, FuncFlags::MACROCALL_NEW }
};
/** Functions defined by OpenFormula, but not supported by Calc or by Excel. */
const FunctionData saFuncTableOdf[] =
{
@ -1008,6 +1022,7 @@ FunctionProviderImpl::FunctionProviderImpl( bool bImportFilter )
initFuncs(saFuncTable2010 , std::end(saFuncTable2010) , bImportFilter);
initFuncs(saFuncTable2013 , std::end(saFuncTable2013) , bImportFilter);
initFuncs(saFuncTable2016 , std::end(saFuncTable2016) , bImportFilter);
initFuncs(saFuncTable2021 , std::end(saFuncTable2021 ), bImportFilter);
initFuncs(saFuncTableOdf , std::end(saFuncTableOdf) , bImportFilter);
initFuncs(saFuncTableOOoLO, std::end(saFuncTableOOoLO), bImportFilter);
}