273 lines
11 KiB
C++
273 lines
11 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <rangecache.hxx>
|
|
#include <cellvalue.hxx>
|
|
#include <document.hxx>
|
|
#include <brdcst.hxx>
|
|
#include <queryevaluator.hxx>
|
|
#include <queryparam.hxx>
|
|
|
|
#include <sal/log.hxx>
|
|
#include <svl/numformat.hxx>
|
|
#include <unotools/collatorwrapper.hxx>
|
|
|
|
static bool needsDescending(ScQueryOp op)
|
|
{
|
|
assert(op == SC_GREATER || op == SC_GREATER_EQUAL || op == SC_LESS || op == SC_LESS_EQUAL
|
|
|| op == SC_EQUAL);
|
|
// We want all matching values to start in the sort order,
|
|
// since the data is searched from start until the last matching one.
|
|
return op == SC_GREATER || op == SC_GREATER_EQUAL;
|
|
}
|
|
|
|
static ScSortedRangeCache::ValueType toValueType(const ScQueryParam& param)
|
|
{
|
|
assert(param.GetEntry(0).bDoQuery && !param.GetEntry(1).bDoQuery
|
|
&& param.GetEntry(0).GetQueryItems().size() == 1);
|
|
assert(param.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString
|
|
|| param.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue);
|
|
if (param.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue)
|
|
return ScSortedRangeCache::ValueType::Values;
|
|
return param.bCaseSens ? ScSortedRangeCache::ValueType::StringsCaseSensitive
|
|
: ScSortedRangeCache::ValueType::StringsCaseInsensitive;
|
|
}
|
|
|
|
ScSortedRangeCache::ScSortedRangeCache(ScDocument* pDoc, const ScRange& rRange,
|
|
const ScQueryParam& param, ScInterpreterContext* context,
|
|
bool invalid, bool bNewSearchFunction,
|
|
sal_uInt8 nSortedBinarySearch)
|
|
: maRange(rRange)
|
|
, mpDoc(pDoc)
|
|
, mValid(false)
|
|
, mRowSearch(param.bByRow)
|
|
, mValueType(toValueType(param))
|
|
{
|
|
if (mRowSearch)
|
|
assert(maRange.aStart.Col() == maRange.aEnd.Col());
|
|
else
|
|
assert(maRange.aStart.Row() == maRange.aEnd.Row());
|
|
assert(maRange.aStart.Tab() == maRange.aEnd.Tab());
|
|
SCTAB nTab = maRange.aStart.Tab();
|
|
assert(param.GetEntry(0).bDoQuery && !param.GetEntry(1).bDoQuery
|
|
&& param.GetEntry(0).GetQueryItems().size() == 1);
|
|
const ScQueryEntry& entry = param.GetEntry(0);
|
|
const ScQueryEntry::Item& item = entry.GetQueryItem();
|
|
mQueryOp = entry.eOp;
|
|
mQueryType = item.meType;
|
|
|
|
if (invalid)
|
|
return; // leave empty
|
|
|
|
SCROW startRow = maRange.aStart.Row();
|
|
SCROW endRow = maRange.aEnd.Row();
|
|
SCCOL startCol = maRange.aStart.Col();
|
|
SCCOL endCol = maRange.aEnd.Col();
|
|
if (!item.mbMatchEmpty)
|
|
if (!pDoc->ShrinkToDataArea(nTab, startCol, startRow, endCol, endRow))
|
|
return; // no data cells, no need for a cache
|
|
|
|
if (mValueType == ValueType::Values)
|
|
{
|
|
struct ColRowData
|
|
{
|
|
SCCOLROW col_row;
|
|
double value;
|
|
};
|
|
|
|
std::vector<ColRowData> colrowData;
|
|
for (SCCOL nCol = startCol; nCol <= endCol; ++nCol)
|
|
{
|
|
for (SCROW nRow = startRow; nRow <= endRow; ++nRow)
|
|
{
|
|
ScRefCellValue cell(pDoc->GetRefCellValue(ScAddress(nCol, nRow, nTab)));
|
|
if (ScQueryEvaluator::isQueryByValue(mQueryOp, mQueryType, cell))
|
|
colrowData.push_back(ColRowData{ mRowSearch ? nRow : nCol, cell.getValue() });
|
|
else if (ScQueryEvaluator::isQueryByString(mQueryOp, mQueryType, cell))
|
|
{
|
|
// Make sure that other possibilities in the generic handling
|
|
// in ScQueryEvaluator::processEntry() do not alter the results.
|
|
// (ByTextColor/ByBackgroundColor are blocked by CanBeUsedForSorterCache(),
|
|
// but isQueryByString() is possible if the cell content is a string.
|
|
// And including strings here would be tricky, as the string comparison
|
|
// may possibly(?) be different than a numeric one. So check if the string
|
|
// may possibly match a number, by converting it to one. If it can't match,
|
|
// then it's fine to ignore it (and it can happen e.g. if the query uses
|
|
// the whole column which includes a textual header). But if it can possibly
|
|
// match, then bail out and leave it to the unoptimized case.
|
|
// TODO Maybe it would actually work to use the numeric value obtained here?
|
|
if (!bNewSearchFunction && !ScQueryEvaluator::isMatchWholeCell(*pDoc, mQueryOp))
|
|
return; // substring matching cannot be sorted, but new search functions are sorted
|
|
sal_uInt32 format = 0;
|
|
double value;
|
|
if (context->NFIsNumberFormat(cell.getString(pDoc), format, value))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nSortedBinarySearch == 0x00) //nBinarySearchDisabled = 0x00
|
|
{
|
|
std::stable_sort(
|
|
colrowData.begin(), colrowData.end(),
|
|
[](const ColRowData& d1, const ColRowData& d2) { return d1.value < d2.value; });
|
|
}
|
|
else if (nSortedBinarySearch == 0x01) //nSearchbAscd
|
|
{
|
|
// expected it is already sorted properly in Ascd mode.
|
|
}
|
|
else /*(nSortedBinarySearch == 0x02) nSearchbDesc*/
|
|
{
|
|
// expected it is already sorted properly in Desc mode, just need to reverse.
|
|
std::reverse(colrowData.begin(), colrowData.end());
|
|
}
|
|
|
|
if (needsDescending(entry.eOp))
|
|
{
|
|
for (auto it = colrowData.rbegin(); it != colrowData.rend(); ++it)
|
|
{
|
|
if (mRowSearch)
|
|
mSortedRows.emplace_back(it->col_row);
|
|
else
|
|
mSortedCols.emplace_back(it->col_row);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const ColRowData& d : colrowData)
|
|
{
|
|
if (mRowSearch)
|
|
mSortedRows.emplace_back(d.col_row);
|
|
else
|
|
mSortedCols.emplace_back(d.col_row);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
struct ColRowData
|
|
{
|
|
SCCOLROW col_row;
|
|
OUString string;
|
|
};
|
|
std::vector<ColRowData> colrowData;
|
|
// Try to reuse as much ScQueryEvaluator code as possible, this should
|
|
// basically do the same comparisons.
|
|
assert(pDoc->FetchTable(nTab) != nullptr);
|
|
ScQueryEvaluator evaluator(*pDoc, *pDoc->FetchTable(nTab), param, context, nullptr,
|
|
bNewSearchFunction);
|
|
for (SCCOL nCol = startCol; nCol <= endCol; ++nCol)
|
|
{
|
|
for (SCROW nRow = startRow; nRow <= endRow; ++nRow)
|
|
{
|
|
ScRefCellValue cell(pDoc->GetRefCellValue(ScAddress(nCol, nRow, nTab)));
|
|
// This should be used only with ScQueryEntry::ByString, and that
|
|
// means that ScQueryEvaluator::isQueryByString() should be the only
|
|
// possibility in the generic handling in ScQueryEvaluator::processEntry()
|
|
// (ByTextColor/ByBackgroundColor are blocked by CanBeUsedForSorterCache(),
|
|
// and isQueryByValue() is blocked by ScQueryEntry::ByString).
|
|
assert(mQueryType == ScQueryEntry::ByString);
|
|
assert(!ScQueryEvaluator::isQueryByValue(mQueryOp, mQueryType, cell));
|
|
if (ScQueryEvaluator::isQueryByString(mQueryOp, mQueryType, cell))
|
|
{
|
|
OUString string = evaluator.getCellString(cell, nRow, nCol);
|
|
colrowData.push_back(ColRowData{ mRowSearch ? nRow : nCol, string });
|
|
}
|
|
}
|
|
}
|
|
CollatorWrapper& collator
|
|
= ScGlobal::GetCollator(mValueType == ValueType::StringsCaseSensitive);
|
|
|
|
if (nSortedBinarySearch == 0x00) //nBinarySearchDisabled = 0x00
|
|
{
|
|
std::stable_sort(colrowData.begin(), colrowData.end(),
|
|
[&collator](const ColRowData& d1, const ColRowData& d2) {
|
|
return collator.compareString(d1.string, d2.string) < 0;
|
|
});
|
|
}
|
|
else if (nSortedBinarySearch == 0x01) //nSearchbAscd
|
|
{
|
|
// expected it is already sorted properly in Asc mode.
|
|
}
|
|
else /*(nSortedBinarySearch == 0x02) nSearchbDesc*/
|
|
{
|
|
// expected it is already sorted properly in Desc mode, just need to reverse.
|
|
std::reverse(colrowData.begin(), colrowData.end());
|
|
}
|
|
|
|
if (needsDescending(entry.eOp))
|
|
{
|
|
for (auto it = colrowData.rbegin(); it != colrowData.rend(); ++it)
|
|
{
|
|
if (mRowSearch)
|
|
mSortedRows.emplace_back(it->col_row);
|
|
else
|
|
mSortedCols.emplace_back(it->col_row);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const ColRowData& d : colrowData)
|
|
{
|
|
if (mRowSearch)
|
|
mSortedRows.emplace_back(d.col_row);
|
|
else
|
|
mSortedCols.emplace_back(d.col_row);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mRowSearch)
|
|
{
|
|
mRowToIndex.resize(maRange.aEnd.Row() - maRange.aStart.Row() + 1, mSortedRows.max_size());
|
|
for (size_t i = 0; i < mSortedRows.size(); ++i)
|
|
mRowToIndex[mSortedRows[i] - maRange.aStart.Row()] = i;
|
|
}
|
|
else
|
|
{
|
|
mColToIndex.resize(maRange.aEnd.Col() - maRange.aStart.Col() + 1, mSortedCols.max_size());
|
|
for (size_t i = 0; i < mSortedCols.size(); ++i)
|
|
mColToIndex[mSortedCols[i] - maRange.aStart.Col()] = i;
|
|
}
|
|
mValid = true;
|
|
}
|
|
|
|
void ScSortedRangeCache::Notify(const SfxHint& rHint)
|
|
{
|
|
if (!mpDoc->IsInDtorClear())
|
|
{
|
|
if (rHint.GetId() == SfxHintId::ScDataChanged || rHint.GetId() == SfxHintId::ScAreaChanged)
|
|
{
|
|
mpDoc->RemoveSortedRangeCache(*this);
|
|
// this ScSortedRangeCache is deleted by RemoveSortedRangeCache
|
|
}
|
|
}
|
|
}
|
|
|
|
ScSortedRangeCache::HashKey ScSortedRangeCache::makeHashKey(const ScRange& range,
|
|
const ScQueryParam& param)
|
|
{
|
|
assert(param.GetEntry(0).bDoQuery && !param.GetEntry(1).bDoQuery
|
|
&& param.GetEntry(0).GetQueryItems().size() == 1);
|
|
const ScQueryEntry& entry = param.GetEntry(0);
|
|
const ScQueryEntry::Item& item = entry.GetQueryItem();
|
|
return { range, toValueType(param), entry.eOp, item.meType };
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|