This is an automated email from the ASF dual-hosted git repository. jimjag pushed a commit to branch openformula in repository https://gitbox.apache.org/repos/asf/openoffice.git
commit 44bd4476f60fd9f48305bf4517478e4a49b01a83 Author: Jim Jagielski <[email protected]> AuthorDate: Fri Nov 13 09:40:30 2026 -0500 sc: replace #N/A element-by-element when IFNA gets an array The fallback is only evaluated if the array holds a #N/A. --- main/sc/source/core/inc/jumpmatrix.hxx | 5 +++ main/sc/source/core/tool/interpr1.cxx | 67 +++++++++++++++++++++++++++++++++- test/odf-validate/calcval-cases14.tsv | 11 ++++++ test/odf-validate/calcval.py | 3 +- 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/main/sc/source/core/inc/jumpmatrix.hxx b/main/sc/source/core/inc/jumpmatrix.hxx index 9af8cc4676..11a852697c 100644 --- a/main/sc/source/core/inc/jumpmatrix.hxx +++ b/main/sc/source/core/inc/jumpmatrix.hxx @@ -69,6 +69,7 @@ class ScJumpMatrix SCSIZE nResMatCols; SCSIZE nResMatRows; bool bStarted; + bool bKeepNoPath; // not implemented, prevent usage ScJumpMatrix( const ScJumpMatrix& ); @@ -86,6 +87,7 @@ public: , nResMatCols( nColsP ) , nResMatRows( nRowsP ) , bStarted( false ) + , bKeepNoPath( false ) { // Initialize result matrix in case of // a premature end of the interpreter @@ -154,6 +156,9 @@ public: nNext, nStop); } } + /// Elements without a jump path keep the value already in the result matrix. + void SetKeepNoPath() { bKeepNoPath = true; } + bool IsKeepNoPath() const { return bKeepNoPath; } void SetJumpParameters( ScTokenVec* p ) { pParams = p; } const ScTokenVec* GetJumpParameters() const { return pParams; } diff --git a/main/sc/source/core/tool/interpr1.cxx b/main/sc/source/core/tool/interpr1.cxx index 36ed4d9f1d..78a4f6216a 100644 --- a/main/sc/source/core/tool/interpr1.cxx +++ b/main/sc/source/core/tool/interpr1.cxx @@ -349,9 +349,70 @@ void ScInterpreter::ScIfna() } break; case svRefList : - case svMatrix : // No per-element #N/A substitution, the value passes through. break; + case svMatrix : + { + ScMatrixRef pMat = PopMatrix(); + SCSIZE nCols = 0, nRows = 0; + if ( pMat ) + pMat->GetDimensions( nCols, nRows ); + if ( !nCols || !nRows ) + { + PushIllegalParameter(); + aCode.Jump( pJump[ 2 ], pJump[ 2 ] ); + return; + } + FormulaTokenRef xNew; + ScTokenMatrixMap::const_iterator aMapIter; + if ( pTokenMatrixMap && ((aMapIter = pTokenMatrixMap->find( pCur )) != pTokenMatrixMap->end()) ) + xNew = (*aMapIter).second; + else + { + bool bAnyNA = false; + for ( SCSIZE nC = 0; nC < nCols && !bAnyNA; ++nC ) + for ( SCSIZE nR = 0; nR < nRows && !bAnyNA; ++nR ) + bAnyNA = pMat->IsValue( nC, nR ) && pMat->GetError( nC, nR ) == NOTAVAILABLE; + if ( !bAnyNA ) + { + // nothing to substitute, the fallback is not evaluated + PushMatrix( pMat ); + aCode.Jump( pJump[ 2 ], pJump[ 2 ] ); + return; + } + // DoubleError handled by JumpMatrix + pMat->SetErrorInterpreter( NULL ); + ScJumpMatrix* pJumpMat = new ScJumpMatrix( nCols, nRows ); + pJumpMat->SetKeepNoPath(); + ScMatrix* pRes = pJumpMat->GetResultMatrix(); + for ( SCSIZE nC = 0; nC < nCols; ++nC ) + { + for ( SCSIZE nR = 0; nR < nRows; ++nR ) + { + if ( pMat->IsValue( nC, nR ) && pMat->GetError( nC, nR ) == NOTAVAILABLE ) + pJumpMat->SetJump( nC, nR, 1.0, pJump[ 1 ], pJump[ 2 ] ); + else + { + ScMatValType nType = 0; + const ScMatrixValue* pMatVal = pMat->Get( nC, nR, nType ); + if ( ScMatrix::IsValueType( nType ) ) + pRes->PutDouble( pMatVal->fVal, nC, nR ); + else if ( ScMatrix::IsRealStringType( nType ) ) + pRes->PutString( pMat->GetString( nC, nR ), nC, nR ); + else + pRes->PutEmpty( nC, nR ); + pJumpMat->SetJump( nC, nR, 1.0, pJump[ 2 ], pJump[ 2 ] ); + } + } + } + xNew = new ScJumpMatrixToken( pJumpMat ); + GetTokenMatrixMap().insert( ScTokenMatrixMap::value_type( pCur, xNew ) ); + } + PushTempToken( xNew ); + // set endpoint of path for main code line + aCode.Jump( pJump[ 2 ], pJump[ 2 ] ); + return; + } default: if ( nGlobalError == NOTAVAILABLE || (GetStackType() == svError && pStack[ sp - 1 ]->GetError() == NOTAVAILABLE) ) @@ -636,7 +697,9 @@ bool ScInterpreter::JumpMatrix( short nStackLevel ) if ( pResMat ) { // a sal_False without path results in an empty path value - if ( fBool == 0.0 ) + if ( pJumpMatrix->IsKeepNoPath() ) + ; // result already in place + else if ( fBool == 0.0 ) pResMat->PutEmptyPath( nC, nR ); else pResMat->PutDouble( fBool, nC, nR ); diff --git a/test/odf-validate/calcval-cases14.tsv b/test/odf-validate/calcval-cases14.tsv index 2683300e46..a40f566121 100644 --- a/test/odf-validate/calcval-cases14.tsv +++ b/test/odf-validate/calcval-cases14.tsv @@ -142,6 +142,17 @@ IFNA.range =IFNA(D1:D3;99) cell 10 6.x range reference, implicit intersection ta IFNA.ref.na =IFNA(AM1;99) cell 99 6.x cell reference containing #N/A (AM1 = NA()), fallback returned IFNA.ref.err =IFNA(AM2;99) cell #ERR 6.x cell reference containing another error (AM2 = 1/0), error propagated IFNA.ref.empty =IFNA(AM4;99) cell 0 6.x reference to an empty cell (AM4 unset) is not #N/A; the empty cell shows as 0 +IFNA.arr.sub =IFNA(AN1:AN6;99) array:1x6 5|99|s:x|99|7|#ERR 6.x array argument: each #N/A element is replaced (AN1:AN6 = 5, #N/A, x, #N/A, 7, 1/0), other errors stay +IFNA.arr.fallback =IFNA(AN1:AN4;D1:D4) array:1x4 5|20|s:x|40 6.x array fallback is taken element by element (D1:D4 = 10, 20, 30, 40) +IFNA.arr.text =IFNA(AN1:AN4;"n") array:1x4 5|s:n|s:x|s:n 6.x text fallback +IFNA.arr.none =IFNA(D1:D3;99) array:1x3 10|20|30 6.x array without #N/A is returned unchanged +IFNA.arr.lazy =IFNA(D1:D3;1/0) array:1x3 10|20|30 6.x fallback not evaluated when no element is #N/A +IFNA.arr.fallerr =IFNA(AN1:AN2;1/0) array:1x2 5|#ERR 6.x fallback evaluated only for the #N/A element +IFNA.arr.inline =IFNA({1;2|3;4};9) array:2x2 1|2|3|4 6.x inline array without #N/A +IFNA.arr.sum =SUM(IFNA(AN1:AN4;0)) array 5 6.x IFNA inside SUM in an array formula; the text element is ignored by SUM +IFNA.arr.nested =IFNA(IFNA(AN1:AN4;AN1:AN4);0) array:1x4 5|0|s:x|0 6.x nested: the inner fallback is #N/A again and the outer IFNA replaces it +IFNA.arr.inif =IF(D1:D3>15;IFNA(AN2:AN4;1);0) array:1x3 0|s:x|1 6.x IFNA array in an IF path (AN2:AN4 = #N/A, x, #N/A) +IFNA.arr.sumprod =SUMPRODUCT(IFNA(AN1:AN2+0;100)) cell - 6.x record only: whether the force-array context reaches the operator inside IFNA SKEWP.two =SKEWP(1;2) cell 0 6.x two observations, non-constant, zero skewness SKEW.text =SKEW(1;"text";2) cell - 6.3.7 a direct Text argument converts as in 6.3.5, implementation-defined (0 or an Error); AOO returns an Error SKEW.err =SKEW(1/0) cell #ERR 6.x regression: error argument propagates diff --git a/test/odf-validate/calcval.py b/test/odf-validate/calcval.py index 319611a422..4262c9ce8c 100644 --- a/test/odf-validate/calcval.py +++ b/test/odf-validate/calcval.py @@ -140,8 +140,9 @@ def setup_data(doc): put("AI1", [[v] for v in (1, 3, 5)]) put("AJ1", [[v] for v in (2, 4, 7, 9)]) put("AK1", [[v] for v in (2, 4, 7, 9)]) - for addr, f in (("AM1", "=NA()"), ("AM2", "=1/0"), ("AM3", '=""')): + for addr, f in (("AM1", "=NA()"), ("AM2", "=1/0"), ("AM3", '=""'), ("AN1", "5"), ("AN2", "=NA()"), ("AN4", "=NA()"), ("AN5", "7"), ("AN6", "=1/0")): sh.getCellRangeByName(addr).setFormula(f) + sh.getCellRangeByName("AN3").setString("x") return sh
