Change-Id: Ia2732cc041367bc113654bc424ea6621f3961e9f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211724 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
552 lines
23 KiB
C++
552 lines
23 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "helper/qahelper.hxx"
|
|
|
|
#include <compiler.hxx>
|
|
#include <formulacell.hxx>
|
|
#include <patattr.hxx>
|
|
#include <scitems.hxx>
|
|
#include <scopetools.hxx>
|
|
#include <tokenarray.hxx>
|
|
|
|
#include <formula/errorcodes.hxx>
|
|
#include <i18nlangtag/lang.h>
|
|
#include <svl/intitem.hxx>
|
|
#include <svl/numformat.hxx>
|
|
#include <svl/zforlist.hxx>
|
|
|
|
#include <memory>
|
|
|
|
using namespace formula;
|
|
|
|
class TestSpilledRange : public ScUcalcTestBase
|
|
{
|
|
};
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperator)
|
|
{
|
|
// The # postfix operator on the origin of a 1x4 matrix formula
|
|
// expands to the spill range, so SUM(A1#) totals all four values.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(1, 0, 0), 10.0);
|
|
m_pDoc->SetValue(ScAddress(1, 1, 0), 20.0);
|
|
m_pDoc->SetValue(ScAddress(1, 2, 0), 30.0);
|
|
m_pDoc->SetValue(ScAddress(1, 3, 0), 40.0);
|
|
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 3, aMark, u"=B1:B4"_ustr);
|
|
|
|
ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0, 0, 0));
|
|
CPPUNIT_ASSERT(pFC);
|
|
CPPUNIT_ASSERT_EQUAL(ScMatrixMode::Formula, pFC->GetMatrixFlag());
|
|
SCCOL nCols = 0;
|
|
SCROW nRows = 0;
|
|
pFC->GetMatColsRows(nCols, nRows);
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(1), nCols);
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), nRows);
|
|
|
|
m_pDoc->SetString(ScAddress(2, 0, 0), u"=SUM(A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(100.0, m_pDoc->GetValue(ScAddress(2, 0, 0)));
|
|
|
|
m_pDoc->SetString(ScAddress(2, 1, 0), u"=COUNT(A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(2, 1, 0)));
|
|
|
|
CPPUNIT_ASSERT_EQUAL(u"=SUM(A1#)"_ustr, m_pDoc->GetFormula(2, 0, 0));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorOnNumberCell)
|
|
{
|
|
// # on a plain value cell reports #REF!. The operator requires
|
|
// its operand to be the master cell of a dynamic-array formula.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(0, 0, 0), 42.0);
|
|
m_pDoc->SetString(ScAddress(1, 0, 0), u"=A1#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoRef, m_pDoc->GetErrCode(ScAddress(1, 0, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorOnStringCell)
|
|
{
|
|
// # on a plain string cell reports #REF!.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetString(ScAddress(0, 0, 0), u"hello"_ustr);
|
|
m_pDoc->SetString(ScAddress(1, 0, 0), u"=A1#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoRef, m_pDoc->GetErrCode(ScAddress(1, 0, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorOnNonMasterFormula)
|
|
{
|
|
// # on a plain formula cell that is not a dynamic-array master
|
|
// falls back to that cell's value. Only a master triggers the
|
|
// spill expansion.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetString(ScAddress(0, 0, 0), u"=1+2"_ustr);
|
|
m_pDoc->SetString(ScAddress(1, 0, 0), u"=A1#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1, 0, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeNeedsOneCellToNameAMaster)
|
|
{
|
|
// The # refers to the master cell before it, so an operand wider than one cell has no
|
|
// master and gives #REF!. A union list is used because a plain range before a # is
|
|
// reduced to the cell it lines up with during the parse and never arrives as a range.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(4, 0, 0), 10.0);
|
|
m_pDoc->SetValue(ScAddress(4, 1, 0), 20.0);
|
|
|
|
// Master at A1 spills =E1:E2 into A1:A2.
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 1, aMark, u"=E1:E2"_ustr);
|
|
|
|
m_pDoc->SetString(ScAddress(6, 0, 0), u"=(A1~E1)#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoRef, m_pDoc->GetErrCode(ScAddress(6, 0, 0)));
|
|
|
|
// The list gets there the same way with the spill range as its second part.
|
|
m_pDoc->SetString(ScAddress(6, 1, 0), u"=(E1~A1)#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoRef, m_pDoc->GetErrCode(ScAddress(6, 1, 0)));
|
|
|
|
// A matrix formula and a dynamic-array master parse without working out implicit
|
|
// intersections, so a range before the # arrives whole in those too.
|
|
m_pDoc->InsertMatrixFormula(6, 2, 6, 2, aMark, u"=(E1:E2)#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoRef, m_pDoc->GetErrCode(ScAddress(6, 2, 0)));
|
|
|
|
// The range is one operand of the #, not a matrix walked cell by cell, so the master
|
|
// gives one error and spills nothing. The value below it would force #SPILL! if it did.
|
|
m_pDoc->SetValue(ScAddress(7, 1, 0), 5.0);
|
|
m_pDoc->InsertMatrixFormula(7, 0, 7, 0, aMark, u"=(E1:E2)#"_ustr, nullptr,
|
|
formula::FormulaGrammar::GRAM_DEFAULT,
|
|
/*bDynamicArrayMaster=*/true);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoRef, m_pDoc->GetErrCode(ScAddress(7, 0, 0)));
|
|
|
|
m_pDoc->SetValue(ScAddress(8, 1, 0), 5.0);
|
|
m_pDoc->InsertMatrixFormula(8, 0, 8, 0, aMark, u"=E1:E2#"_ustr, nullptr,
|
|
formula::FormulaGrammar::GRAM_DEFAULT,
|
|
/*bDynamicArrayMaster=*/true);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoRef, m_pDoc->GetErrCode(ScAddress(8, 0, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorImplicitIntersection)
|
|
{
|
|
// A # reference in a cell that does not promote to a dynamic-array
|
|
// master reduces by implicit intersection on the spill range. The
|
|
// cell takes the slot of the spill range aligned with the formula
|
|
// position, or #VALUE! when the formula sits outside the spill
|
|
// range. Cells set through SetString are not eligible for
|
|
// promotion, so they take this path.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
// Master at A1 spills =B1:B3 with 10, 20 and 30 into A1:A3.
|
|
m_pDoc->SetValue(ScAddress(1, 0, 0), 10.0);
|
|
m_pDoc->SetValue(ScAddress(1, 1, 0), 20.0);
|
|
m_pDoc->SetValue(ScAddress(1, 2, 0), 30.0);
|
|
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 2, aMark, u"=B1:B3"_ustr);
|
|
|
|
// The reference cells E1..E3 sit in rows aligned with the spill
|
|
// range. Each picks the slot of A1# matching its own row. E4
|
|
// sits one row past the spill range, so the cell reports
|
|
// #VALUE! through implicit intersection failure.
|
|
m_pDoc->SetString(ScAddress(4, 0, 0), u"=A1#"_ustr);
|
|
m_pDoc->SetString(ScAddress(4, 1, 0), u"=A1#"_ustr);
|
|
m_pDoc->SetString(ScAddress(4, 2, 0), u"=A1#"_ustr);
|
|
m_pDoc->SetString(ScAddress(4, 3, 0), u"=A1#"_ustr);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(4, 0, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(20.0, m_pDoc->GetValue(ScAddress(4, 1, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(30.0, m_pDoc->GetValue(ScAddress(4, 2, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoValue, m_pDoc->GetErrCode(ScAddress(4, 3, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorRespills)
|
|
{
|
|
// A freshly typed =A1# intends an array result, so it promotes to
|
|
// a dynamic-array master and spills the referenced range again
|
|
// instead of collapsing to the row-aligned value.
|
|
m_pDoc->SetAutoCalc(false);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(1, 0, 0), 10.0);
|
|
m_pDoc->SetValue(ScAddress(1, 1, 0), 20.0);
|
|
m_pDoc->SetValue(ScAddress(1, 2, 0), 30.0);
|
|
|
|
// Master at A1 spills =B1:B3 into A1:A3.
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 2, aMark, u"=B1:B3"_ustr);
|
|
|
|
// Enter =A1# at E1 the way the UI does: eligible for promotion.
|
|
ScAddress aRefPos(4, 0, 0);
|
|
ScCompiler aComp(*m_pDoc, aRefPos, m_pDoc->GetGrammar(), false, false);
|
|
std::unique_ptr<ScTokenArray> pCode = aComp.CompileString(u"=A1#"_ustr);
|
|
auto pRef = new ScFormulaCell(*m_pDoc, aRefPos, std::move(pCode));
|
|
pRef->SetAutoDynamicArrayEligible(true);
|
|
m_pDoc->SetFormulaCell(aRefPos, pRef);
|
|
|
|
m_pDoc->SetAutoCalc(true);
|
|
m_pDoc->CalcAll();
|
|
|
|
// E1 became a master covering three rows and spilled its values
|
|
// into E1:E3.
|
|
CPPUNIT_ASSERT(pRef->IsDynamicArrayMaster());
|
|
SCCOL nCols = 0;
|
|
SCROW nRows = 0;
|
|
pRef->GetMatColsRows(nCols, nRows);
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(1), nCols);
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), nRows);
|
|
CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(4, 0, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(20.0, m_pDoc->GetValue(ScAddress(4, 1, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(30.0, m_pDoc->GetValue(ScAddress(4, 2, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorIsRef)
|
|
{
|
|
// A # spilled-range reference answers ISREF as a reference, even
|
|
// though it evaluates to a matrix. A plain computed matrix is not
|
|
// a reference.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(1, 0, 0), 10.0);
|
|
m_pDoc->SetValue(ScAddress(1, 1, 0), 20.0);
|
|
|
|
// Master at A1 spills =B1:B2 into A1:A2.
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 1, aMark, u"=B1:B2"_ustr);
|
|
|
|
m_pDoc->SetString(ScAddress(4, 0, 0), u"=ISREF(A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(4, 0, 0)));
|
|
|
|
// A matrix that carries no source range stays a non-reference.
|
|
m_pDoc->SetString(ScAddress(4, 1, 0), u"=ISREF(MUNIT(2))"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(4, 1, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorIsFormula)
|
|
{
|
|
// ISFORMULA over a # spilled-range reference reports per cell of the
|
|
// spill range, the same as over the plain range: the master is a
|
|
// formula, the cells it spilled into are not.
|
|
m_pDoc->SetAutoCalc(false);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetString(ScAddress(0, 0, 0), u"=1+10"_ustr);
|
|
m_pDoc->SetString(ScAddress(0, 1, 0), u"=1+20"_ustr);
|
|
m_pDoc->SetString(ScAddress(0, 2, 0), u"=1+30"_ustr);
|
|
|
|
// B2 spills =A1:A3 into B2:B4 as a dynamic-array master.
|
|
ScAddress aPos(1, 1, 0);
|
|
ScCompiler aComp(*m_pDoc, aPos, m_pDoc->GetGrammar(), false, false);
|
|
std::unique_ptr<ScTokenArray> pCode = aComp.CompileString(u"=A1:A3"_ustr);
|
|
auto pCell = new ScFormulaCell(*m_pDoc, aPos, std::move(pCode));
|
|
pCell->SetAutoDynamicArrayEligible(true);
|
|
m_pDoc->SetFormulaCell(aPos, pCell);
|
|
m_pDoc->SetAutoCalc(true);
|
|
m_pDoc->CalcAll();
|
|
CPPUNIT_ASSERT(pCell->IsDynamicArrayMaster());
|
|
|
|
// In an array context, ISFORMULA(B2#) gives one formula (the master)
|
|
// then two non-formula spilled cells, matching ISFORMULA(B2:B4).
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(3, 0, 3, 2, aMark, u"=ISFORMULA(B2#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(3, 0, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(3, 1, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(3, 2, 0)));
|
|
|
|
// In a plain cell it reduces by implicit intersection to the
|
|
// row-aligned cell: the master row is a formula, a spilled row is
|
|
// not.
|
|
m_pDoc->SetString(ScAddress(5, 1, 0), u"=ISFORMULA(B2#)"_ustr);
|
|
m_pDoc->SetString(ScAddress(5, 2, 0), u"=ISFORMULA(B2#)"_ustr);
|
|
m_pDoc->CalcAll();
|
|
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(5, 1, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(5, 2, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorInheritsFormat)
|
|
{
|
|
// A # reference that collapses to a single value keeps the number
|
|
// format of the spill-range cell it resolves to, the same as a
|
|
// plain reference to that cell would. A fractional value also
|
|
// survives without being truncated to a whole number.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(1, 0, 0), 43831.5);
|
|
m_pDoc->SetValue(ScAddress(1, 1, 0), 43832.5);
|
|
m_pDoc->SetValue(ScAddress(1, 2, 0), 43833.5);
|
|
|
|
// Master at A1 spills =B1:B3 into A1:A3.
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 2, aMark, u"=B1:B3"_ustr);
|
|
|
|
// Give the spill cells a date format.
|
|
SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
|
|
sal_uInt32 nDateFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMYYYY, LANGUAGE_ENGLISH_US);
|
|
ScPatternAttr aFormatAttr(m_pDoc->getCellAttributeHelper());
|
|
aFormatAttr.ItemSetPut(SfxUInt32Item(ATTR_VALUE_FORMAT, nDateFormat));
|
|
m_pDoc->ApplyPatternAreaTab(0, 0, 0, 2, 0, aFormatAttr);
|
|
|
|
// E2 collapses A1# to the row-aligned slot, keeping its value and
|
|
// date format.
|
|
m_pDoc->SetString(ScAddress(4, 1, 0), u"=A1#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(43832.5, m_pDoc->GetValue(ScAddress(4, 1, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(SvNumFormatType::DATE,
|
|
pFormatter->GetType(m_pDoc->GetNumberFormat(4, 1, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorMultiColumn)
|
|
{
|
|
// # on the origin of a 2x3 matrix expands to all six cells.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(2, 0, 0), 1.0);
|
|
m_pDoc->SetValue(ScAddress(3, 0, 0), 2.0);
|
|
m_pDoc->SetValue(ScAddress(2, 1, 0), 3.0);
|
|
m_pDoc->SetValue(ScAddress(3, 1, 0), 4.0);
|
|
m_pDoc->SetValue(ScAddress(2, 2, 0), 5.0);
|
|
m_pDoc->SetValue(ScAddress(3, 2, 0), 6.0);
|
|
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 1, 2, aMark, u"=C1:D3"_ustr);
|
|
|
|
m_pDoc->SetString(ScAddress(4, 0, 0), u"=SUM(A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(21.0, m_pDoc->GetValue(ScAddress(4, 0, 0)));
|
|
|
|
m_pDoc->SetString(ScAddress(4, 1, 0), u"=ROWS(A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(4, 1, 0)));
|
|
|
|
m_pDoc->SetString(ScAddress(4, 2, 0), u"=COLUMNS(A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(4, 2, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorOnNonMasterMatrixCell)
|
|
{
|
|
// # on a non-master slot of a spilled matrix reports #REF!. Only
|
|
// the master cell of the dynamic-array formula is a valid operand
|
|
// for the spill expansion.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(1, 0, 0), 10.0);
|
|
m_pDoc->SetValue(ScAddress(1, 1, 0), 20.0);
|
|
m_pDoc->SetValue(ScAddress(1, 2, 0), 30.0);
|
|
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 2, aMark, u"=B1:B3"_ustr);
|
|
|
|
// A2 is a non-master slot of the A1:A3 spill range, so =A2# is
|
|
// an illegal reference.
|
|
m_pDoc->SetString(ScAddress(2, 0, 0), u"=A2#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoRef, m_pDoc->GetErrCode(ScAddress(2, 0, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorEmptyCell)
|
|
{
|
|
// # on an empty cell reports #REF!. An empty cell is not a
|
|
// dynamic-array master, so the operator has no spill range
|
|
// to expand.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetString(ScAddress(1, 0, 0), u"=A1#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoRef, m_pDoc->GetErrCode(ScAddress(1, 0, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorInExpression)
|
|
{
|
|
// # composes with arithmetic and aggregating functions.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(1, 0, 0), 5.0);
|
|
m_pDoc->SetValue(ScAddress(1, 1, 0), 15.0);
|
|
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 1, aMark, u"=B1:B2"_ustr);
|
|
|
|
m_pDoc->SetString(ScAddress(2, 0, 0), u"=SUM(A1#)+100"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(120.0, m_pDoc->GetValue(ScAddress(2, 0, 0)));
|
|
|
|
CPPUNIT_ASSERT_EQUAL(u"=SUM(A1#)+100"_ustr, m_pDoc->GetFormula(2, 0, 0));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeKeepsItsPlaceThroughAnOperator)
|
|
{
|
|
// An operator over a # spilled range gives one value per cell, so the result sits at
|
|
// that range.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(4, 0, 0), 100.0);
|
|
m_pDoc->SetValue(ScAddress(4, 1, 0), 200.0);
|
|
m_pDoc->SetValue(ScAddress(4, 2, 0), 400.0);
|
|
|
|
// Master at A1 spills =E1:E3 into A1:A3.
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 2, aMark, u"=E1:E3"_ustr);
|
|
|
|
// Each cell takes the percent of the spilled value in its own row.
|
|
m_pDoc->SetString(ScAddress(6, 0, 0), u"=A1#%"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(6, 0, 0)));
|
|
m_pDoc->SetString(ScAddress(6, 1, 0), u"=A1#%"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(6, 1, 0)));
|
|
|
|
// One row past the spill range there is no value to line up with.
|
|
m_pDoc->SetString(ScAddress(6, 3, 0), u"=A1#%"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NoValue, m_pDoc->GetErrCode(ScAddress(6, 3, 0)));
|
|
|
|
// SUM takes an array, so it gets all the percents, not just one.
|
|
m_pDoc->SetString(ScAddress(7, 0, 0), u"=SUM(A1#%)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(7, 0, 0)));
|
|
|
|
// An explicit @ takes the first percent, not the row's, as they are not the cells' own values.
|
|
m_pDoc->SetString(ScAddress(9, 1, 0), u"=@(A1#%)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(9, 1, 0)));
|
|
|
|
// The spill range references its cells. The percents are the operator's own values, so
|
|
// they sit at that range without referencing it.
|
|
m_pDoc->SetString(ScAddress(8, 0, 0), u"=ISREF(A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(8, 0, 0)));
|
|
m_pDoc->SetString(ScAddress(8, 1, 0), u"=ISREF(A1#%)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(8, 1, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeAsReferenceOperatorOperand)
|
|
{
|
|
// The reference operators take a # spilled range as the range it stands for. They pop it
|
|
// as a matrix that carries the range, and since they work on ranges they use the range
|
|
// and leave the values alone.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetValue(ScAddress(4, 0, 0), 10.0);
|
|
m_pDoc->SetValue(ScAddress(4, 1, 0), 20.0);
|
|
m_pDoc->SetValue(ScAddress(4, 2, 0), 30.0);
|
|
|
|
// Master at A1 spills =E1:E3 into A1:A3.
|
|
ScMarkData aMark(m_pDoc->GetSheetLimits());
|
|
aMark.SelectOneTable(0);
|
|
m_pDoc->InsertMatrixFormula(0, 0, 0, 2, aMark, u"=E1:E3"_ustr);
|
|
|
|
// B2 and C1 sit outside the spill range, so each operator's result is a distinct sum.
|
|
m_pDoc->SetValue(ScAddress(1, 1, 0), 7.0);
|
|
m_pDoc->SetValue(ScAddress(2, 0, 0), 5.0);
|
|
|
|
// The intersection of A1:C2 with the spill range A1:A3 is A1:A2, whichever side the
|
|
// spill range is on.
|
|
m_pDoc->SetString(ScAddress(6, 0, 0), u"=SUM(A1:C2!A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(30.0, m_pDoc->GetValue(ScAddress(6, 0, 0)));
|
|
// The parentheses keep the # away from the intersection operator, because a # directly
|
|
// before an exclamation mark lexes as the start of an error constant.
|
|
m_pDoc->SetString(ScAddress(7, 0, 0), u"=SUM((A1#)!A1:C2)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(30.0, m_pDoc->GetValue(ScAddress(7, 0, 0)));
|
|
|
|
// The range operator spans the spill range and C1, which is A1:C3, so B2 comes in.
|
|
m_pDoc->SetString(ScAddress(6, 1, 0), u"=SUM(A1#:C1)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(72.0, m_pDoc->GetValue(ScAddress(6, 1, 0)));
|
|
// The parentheses keep the left operand apart from the spill range, because C1:A1 on its
|
|
// own lexes as a single reference instead of two operands of the range operator.
|
|
m_pDoc->SetString(ScAddress(7, 1, 0), u"=SUM((C1):A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(FormulaError::NONE, m_pDoc->GetErrCode(ScAddress(7, 1, 0)));
|
|
CPPUNIT_ASSERT_EQUAL(72.0, m_pDoc->GetValue(ScAddress(7, 1, 0)));
|
|
|
|
// The union operator keeps the parts apart, so B2 stays out.
|
|
m_pDoc->SetString(ScAddress(6, 2, 0), u"=SUM(A1#~C1)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(65.0, m_pDoc->GetValue(ScAddress(6, 2, 0)));
|
|
m_pDoc->SetString(ScAddress(7, 2, 0), u"=SUM(C1~A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(65.0, m_pDoc->GetValue(ScAddress(7, 2, 0)));
|
|
|
|
// With a third part the union operator extends a list it already built, which is a
|
|
// separate code path, so check that the spill range gets there too.
|
|
m_pDoc->SetString(ScAddress(6, 3, 0), u"=SUM(C1~B2~A1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(72.0, m_pDoc->GetValue(ScAddress(6, 3, 0)));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_TEST_FIXTURE(TestSpilledRange, testSpilledRangeOperatorFormulaStringRoundtrip)
|
|
{
|
|
// The # operator round-trips through GetFormula across several
|
|
// common shapes: standalone, inside SUM/AVERAGE, and on both
|
|
// operands of a binary expression.
|
|
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
|
|
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
|
|
|
|
m_pDoc->SetString(ScAddress(0, 0, 0), u"=A2#"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(u"=A2#"_ustr, m_pDoc->GetFormula(0, 0, 0));
|
|
|
|
m_pDoc->SetString(ScAddress(0, 1, 0), u"=SUM(B1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(u"=SUM(B1#)"_ustr, m_pDoc->GetFormula(0, 1, 0));
|
|
|
|
m_pDoc->SetString(ScAddress(0, 2, 0), u"=AVERAGE(C5#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(u"=AVERAGE(C5#)"_ustr, m_pDoc->GetFormula(0, 2, 0));
|
|
|
|
m_pDoc->SetString(ScAddress(0, 3, 0), u"=MAX(D1#)+MIN(D1#)"_ustr);
|
|
CPPUNIT_ASSERT_EQUAL(u"=MAX(D1#)+MIN(D1#)"_ustr, m_pDoc->GetFormula(0, 3, 0));
|
|
|
|
m_pDoc->DeleteTab(0);
|
|
}
|
|
|
|
CPPUNIT_PLUGIN_IMPLEMENT();
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|