sc: take the implicit intersection of an external range

=@'file:///source.ods'#Sheet1.B:B showed the first cell of the
column and not the one in the formula's own row. ScSingleValue
leaves an external range on the stack unchanged, and Interpret ends
with it on the matrix path, so the result became the top left
element of a matrix over the whole range.

A sheet local range takes a position dependent implicit intersection
in both places, with DoubleRefToPosSingleRef. An external range now
does the same through PopExternalDoubleRefPushSingleRef, which reads
the position from ScCompiler::ExternalDoubleRefToPosSingleRef.

Where the position does not intersect the range nothing changes,
so an unresolved link keeps its own error instead of turning into
a #VALUE!, as testExternalRefUnresolved expects. The external and
the sheet local case still differ there (TODO).

Follow-up of commit: I3baf826177fbeeb4616f407d653f1457a2fbfa8f
(sc: don't build a JumpMatrix for an entire external column)

Change-Id: I68cc88b5bb533cfcd19537be6705e8a64834af64
Signed-off-by: Balazs Varga <balazs.varga@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12614
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 596a329b2f0e888047069b442f4da507a0c668f8)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211105
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Balazs Varga
2026-09-18 14:24:12 +02:00
committed by Xisco Fauli
parent 6e785d7b0b
commit 54378c2b97
7 changed files with 79 additions and 21 deletions
+5
View File
@@ -524,6 +524,11 @@ public:
static bool DoubleRefToPosSingleRefScalarCase(const ScRange& rRange, ScAddress& rAdr,
const ScAddress& rFormulaPos);
/** The same for a range in another document, where the shape of the range
decides and not whether the formula position lies within it. */
static bool ExternalDoubleRefToPosSingleRef(const ScRange& rRange, ScAddress& rAdr,
const ScAddress& rFormulaPos);
bool HasUnhandledPossibleImplicitIntersections() const { return !mUnhandledPossibleImplicitIntersections.empty(); }
SC_DLLPUBLIC static OUString SanitizeDefinedName(const OUString& rStr, const ScDocument& rDoc);
+13
View File
@@ -657,6 +657,19 @@ CPPUNIT_TEST_FIXTURE(ScFiltersTest2, testExternalRefWholeColumnXLOOKUP)
CPPUNIT_ASSERT_EQUAL(u"#N/A"_ustr, pDoc->GetString(ScAddress(1, 9, 0)));
}
CPPUNIT_TEST_FIXTURE(ScFiltersTest2, testExternalRefWholeColumnResult)
{
createScDoc("xlsx/external-ref-wholecol-xlookup.xlsx");
ScDocument* pDoc = getScDoc();
// Excel's cached results are applied on import, recalculate to run the formulas.
pDoc->CalcAll();
// D2:D6 are @ on an entire external column, each row takes its own.
for (SCROW nRow = 1; nRow <= 5; ++nRow)
CPPUNIT_ASSERT_EQUAL(9.0 + nRow, pDoc->GetValue(ScAddress(3, nRow, 0)));
}
CPPUNIT_TEST_FIXTURE(ScFiltersTest2, testExternalRefCacheODS)
{
createScDoc("ods/external-ref-cache.ods");
+4
View File
@@ -2001,6 +2001,10 @@ void TestFormula2::testExtRefImplicitIntersection(ScDocument* pDoc, ScDocument&
rExtDoc.SetValue(ScAddress(1, nRow, 0), (nRow + 1.0) * 10);
}
// @ takes the same intersection, here where its result feeds an operator.
pDoc->SetString(ScAddress(0, 1, 0), u"=@'file:///extdata.fake'#Data.A1:A5*1"_ustr);
CPPUNIT_ASSERT_EQUAL(2.0, pDoc->GetValue(ScAddress(0, 1, 0)));
// A scalar Value parameter takes an implicit intersection, row 3 looks up A3.
pDoc->SetString(ScAddress(0, 2, 0), u"=XLOOKUP('file:///extdata.fake'#Data.A1:A5;"
u"'file:///extdata.fake'#Data.A1:A5;"
+4
View File
@@ -468,6 +468,10 @@ private:
bool PopDoubleRefOrSingleRef( ScAddress& rAdr );
void PopDoubleRefPushMatrix();
void PopRefListPushMatrixOrRef();
/** Pop an external double reference and push the single reference its
implicit intersection yields. Leaves the stack alone and returns false
when the formula position does not intersect the range. */
bool PopExternalDoubleRefPushSingleRef();
// If MatrixFormula: convert svDoubleRef to svMatrix, create JumpMatrix.
// Else convert area reference parameters marked as ForceArray to array.
// Returns true if JumpMatrix created.
+23
View File
@@ -7151,6 +7151,29 @@ bool ScCompiler::DoubleRefToPosSingleRefScalarCase(const ScRange& rRange, ScAddr
return bOk;
}
bool ScCompiler::ExternalDoubleRefToPosSingleRef(const ScRange& rRange, ScAddress& rAdr,
const ScAddress& rFormulaPos)
{
SCCOL nCol = rFormulaPos.Col();
SCROW nRow = rFormulaPos.Row();
if (rRange.aStart == rRange.aEnd)
{
nCol = rRange.aStart.Col();
nRow = rRange.aStart.Row();
}
else if (rRange.aStart.Col() == rRange.aEnd.Col() && rRange.aStart.Row() <= nRow
&& nRow <= rRange.aEnd.Row())
nCol = rRange.aStart.Col();
else if (rRange.aStart.Row() == rRange.aEnd.Row() && rRange.aStart.Col() <= nCol
&& nCol <= rRange.aEnd.Col())
nRow = rRange.aStart.Row();
else
return false;
rAdr.Set(nCol, nRow, rRange.aStart.Tab());
return true;
}
static void lcl_GetColRowDeltas(const ScRange& rRange, SCCOL& rXDelta, SCROW& rYDelta)
{
rXDelta = rRange.aEnd.Col() - rRange.aStart.Col();
+3
View File
@@ -1605,6 +1605,9 @@ void ScInterpreter::ScSingleValue()
PushDouble(GetCellValue(aAdr, aCell));
return;
}
// Same intersection as for a sheet local range above.
if (eType == svExternalDoubleRef && PopExternalDoubleRefPushSingleRef())
return;
if (eType == svMatrix)
{
ScMatrixRef pMat = GetMatrix();
+27 -21
View File
@@ -1467,6 +1467,25 @@ bool ScInterpreter::PopDoubleRefOrSingleRef( ScAddress& rAdr )
return false;
}
bool ScInterpreter::PopExternalDoubleRefPushSingleRef()
{
const ScExternalDoubleRefToken* pToken
= static_cast<const ScExternalDoubleRefToken*>(pStack[sp - 1]);
ScComplexRefData aData( pToken->GetDoubleRef());
const ScRange aRange = aData.toAbs(mrDoc, aPos);
ScAddress aAdr;
if (!ScCompiler::ExternalDoubleRefToPosSingleRef( aRange, aAdr, aPos))
return false;
ScSingleRefData aRefData;
aRefData.InitAddress( aAdr);
FormulaTokenRef xNew = new ScExternalSingleRefToken(
pToken->GetFileId(), pToken->GetTableName(), aRefData);
Pop();
PushTempTokenWithoutError( xNew.get());
return true;
}
void ScInterpreter::PopDoubleRefPushMatrix()
{
if ( GetStackType() == svDoubleRef )
@@ -1693,32 +1712,14 @@ bool ScInterpreter::ConvertMatrixParameters()
auto pEDRToken = static_cast<const ScExternalDoubleRefToken*>(p);
ScComplexRefData aData( pEDRToken->GetDoubleRef());
const ScRange aRange = aData.toAbs(mrDoc, aPos);
// Resolve from the shape of the range.
// DoubleRefToPosSingleRefScalarCase() asks instead
// whether the formula position lies within the range.
SCCOL nCol = aPos.Col();
SCROW nRow = aPos.Row();
bool bOk = true;
if (aRange.aStart == aRange.aEnd)
{
nCol = aRange.aStart.Col();
nRow = aRange.aStart.Row();
}
else if (aRange.aStart.Col() == aRange.aEnd.Col() &&
aRange.aStart.Row() <= nRow && nRow <= aRange.aEnd.Row())
nCol = aRange.aStart.Col();
else if (aRange.aStart.Row() == aRange.aEnd.Row() &&
aRange.aStart.Col() <= nCol && nCol <= aRange.aEnd.Col())
nRow = aRange.aStart.Row();
else
bOk = false;
if (!bOk)
ScAddress aAdr;
if (!ScCompiler::ExternalDoubleRefToPosSingleRef( aRange, aAdr, aPos))
SetError( FormulaError::NoValue);
else
{
ScSingleRefData aRefData;
// Absolute, an external table reference must be.
aRefData.InitAddress( nCol, nRow, aRange.aStart.Tab());
aRefData.InitAddress( aAdr);
formula::FormulaToken* pNew = new ScExternalSingleRefToken(
pEDRToken->GetFileId(), pEDRToken->GetTableName(), aRefData);
pNew->IncRef();
@@ -4584,6 +4585,11 @@ StackVar ScInterpreter::Interpret()
PopRefListPushMatrixOrRef();
pCur = pStack[ sp-1 ];
}
// An external range takes the same implicit intersection as
// the svDoubleRef case below, which nothing else does for it.
if (!bMatrixFormula && pCur->GetType() == svExternalDoubleRef
&& PopExternalDoubleRefPushSingleRef())
pCur = pStack[ sp-1 ];
switch( pCur->GetType() )
{
case svEmptyCell: