tdf#154285 Check upper bound of arguments in SbRtl_Minute function
[LibreOffice.git] / sc / source / core / tool / interpr1.cxx
blob2116cab1297f5909f655e42c9b7706426566da5a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <interpre.hxx>
22 #include <scitems.hxx>
23 #include <editeng/langitem.hxx>
24 #include <editeng/justifyitem.hxx>
25 #include <o3tl/safeint.hxx>
26 #include <o3tl/temporary.hxx>
27 #include <osl/thread.h>
28 #include <unotools/textsearch.hxx>
29 #include <svl/numformat.hxx>
30 #include <svl/zforlist.hxx>
31 #include <svl/zformat.hxx>
32 #include <tools/urlobj.hxx>
33 #include <unotools/charclass.hxx>
34 #include <sfx2/docfile.hxx>
35 #include <sfx2/printer.hxx>
36 #include <unotools/collatorwrapper.hxx>
37 #include <unotools/transliterationwrapper.hxx>
38 #include <rtl/character.hxx>
39 #include <rtl/ustring.hxx>
40 #include <sal/log.hxx>
41 #include <osl/diagnose.h>
42 #include <unicode/uchar.h>
43 #include <unicode/regex.h>
44 #include <i18nlangtag/mslangid.hxx>
46 #include <patattr.hxx>
47 #include <global.hxx>
48 #include <document.hxx>
49 #include <dociter.hxx>
50 #include <docsh.hxx>
51 #include <sfx2/linkmgr.hxx>
52 #include <formulacell.hxx>
53 #include <scmatrix.hxx>
54 #include <docoptio.hxx>
55 #include <attrib.hxx>
56 #include <jumpmatrix.hxx>
57 #include <cellkeytranslator.hxx>
58 #include <lookupcache.hxx>
59 #include <rangenam.hxx>
60 #include <rangeutl.hxx>
61 #include <compiler.hxx>
62 #include <externalrefmgr.hxx>
63 #include <doubleref.hxx>
64 #include <queryparam.hxx>
65 #include <queryiter.hxx>
66 #include <tokenarray.hxx>
67 #include <compare.hxx>
68 #include <comphelper/lok.hxx>
69 #include <comphelper/processfactory.hxx>
70 #include <comphelper/string.hxx>
71 #include <svl/sharedstringpool.hxx>
73 #include <stdlib.h>
74 #include <memory>
75 #include <vector>
76 #include <limits>
77 #include <string_view>
78 #include <cmath>
80 const sal_uInt64 n2power48 = SAL_CONST_UINT64( 281474976710656); // 2^48
82 ScCalcConfig *ScInterpreter::mpGlobalConfig = nullptr;
84 using namespace formula;
85 using ::std::unique_ptr;
87 void ScInterpreter::ScIfJump()
89 const short* pJump = pCur->GetJump();
90 short nJumpCount = pJump[ 0 ];
91 MatrixJumpConditionToMatrix();
92 switch ( GetStackType() )
94 case svMatrix:
96 ScMatrixRef pMat = PopMatrix();
97 if ( !pMat )
98 PushIllegalParameter();
99 else
101 FormulaConstTokenRef xNew;
102 ScTokenMatrixMap::const_iterator aMapIter;
103 // DoubleError handled by JumpMatrix
104 pMat->SetErrorInterpreter( nullptr);
105 SCSIZE nCols, nRows;
106 pMat->GetDimensions( nCols, nRows );
107 if ( nCols == 0 || nRows == 0 )
109 PushIllegalArgument();
110 return;
112 else if ((aMapIter = maTokenMatrixMap.find( pCur)) != maTokenMatrixMap.end())
113 xNew = (*aMapIter).second;
114 else
116 std::shared_ptr<ScJumpMatrix> pJumpMat( std::make_shared<ScJumpMatrix>(
117 pCur->GetOpCode(), nCols, nRows));
118 for ( SCSIZE nC=0; nC < nCols; ++nC )
120 for ( SCSIZE nR=0; nR < nRows; ++nR )
122 double fVal;
123 bool bTrue;
124 bool bIsValue = pMat->IsValue(nC, nR);
125 if (bIsValue)
127 fVal = pMat->GetDouble(nC, nR);
128 bIsValue = std::isfinite(fVal);
129 bTrue = bIsValue && (fVal != 0.0);
130 if (bTrue)
131 fVal = 1.0;
133 else
135 // Treat empty and empty path as 0, but string
136 // as error. ScMatrix::IsValueOrEmpty() returns
137 // true for any empty, empty path, empty cell,
138 // empty result.
139 bIsValue = pMat->IsValueOrEmpty(nC, nR);
140 bTrue = false;
141 fVal = (bIsValue ? 0.0 : CreateDoubleError( FormulaError::NoValue));
143 if ( bTrue )
144 { // TRUE
145 if( nJumpCount >= 2 )
146 { // THEN path
147 pJumpMat->SetJump( nC, nR, fVal,
148 pJump[ 1 ],
149 pJump[ nJumpCount ]);
151 else
152 { // no parameter given for THEN
153 pJumpMat->SetJump( nC, nR, fVal,
154 pJump[ nJumpCount ],
155 pJump[ nJumpCount ]);
158 else
159 { // FALSE
160 if( nJumpCount == 3 && bIsValue )
161 { // ELSE path
162 pJumpMat->SetJump( nC, nR, fVal,
163 pJump[ 2 ],
164 pJump[ nJumpCount ]);
166 else
167 { // no parameter given for ELSE,
168 // or DoubleError
169 pJumpMat->SetJump( nC, nR, fVal,
170 pJump[ nJumpCount ],
171 pJump[ nJumpCount ]);
176 xNew = new ScJumpMatrixToken(std::move(pJumpMat));
177 GetTokenMatrixMap().emplace(pCur, xNew);
179 if (!xNew)
181 PushIllegalArgument();
182 return;
184 PushTokenRef( xNew);
185 // set endpoint of path for main code line
186 aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
189 break;
190 default:
192 const bool bCondition = GetBool();
193 if (nGlobalError != FormulaError::NONE)
194 { // Propagate error, not THEN- or ELSE-path, jump behind.
195 PushError(nGlobalError);
196 aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
198 else if ( bCondition )
199 { // TRUE
200 if( nJumpCount >= 2 )
201 { // THEN path
202 aCode.Jump( pJump[ 1 ], pJump[ nJumpCount ] );
204 else
205 { // no parameter given for THEN
206 nFuncFmtType = SvNumFormatType::LOGICAL;
207 PushInt(1);
208 aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
211 else
212 { // FALSE
213 if( nJumpCount == 3 )
214 { // ELSE path
215 aCode.Jump( pJump[ 2 ], pJump[ nJumpCount ] );
217 else
218 { // no parameter given for ELSE
219 nFuncFmtType = SvNumFormatType::LOGICAL;
220 PushInt(0);
221 aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
228 /** Store a matrix value in another matrix in the context of that other matrix
229 is the result matrix of a jump matrix. All arguments must be valid and are
230 not checked. */
231 static void lcl_storeJumpMatResult(
232 const ScMatrix* pMat, ScJumpMatrix* pJumpMat, SCSIZE nC, SCSIZE nR )
234 if ( pMat->IsValue( nC, nR ) )
236 double fVal = pMat->GetDouble( nC, nR );
237 pJumpMat->PutResultDouble( fVal, nC, nR );
239 else if ( pMat->IsEmpty( nC, nR ) )
241 pJumpMat->PutResultEmpty( nC, nR );
243 else
245 pJumpMat->PutResultString(pMat->GetString(nC, nR), nC, nR);
249 void ScInterpreter::ScIfError( bool bNAonly )
251 const short* pJump = pCur->GetJump();
252 short nJumpCount = pJump[ 0 ];
253 if (!sp || nJumpCount != 2)
255 // Reset nGlobalError here to not propagate the old error, if any.
256 nGlobalError = (sp ? FormulaError::ParameterExpected : FormulaError::UnknownStackVariable);
257 PushError( nGlobalError);
258 aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
259 return;
262 FormulaConstTokenRef xToken( pStack[ sp - 1 ] );
263 bool bError = false;
264 FormulaError nOldGlobalError = nGlobalError;
265 nGlobalError = FormulaError::NONE;
267 MatrixJumpConditionToMatrix();
268 switch (GetStackType())
270 default:
271 Pop();
272 // Act on implicitly propagated error, if any.
273 if (nOldGlobalError != FormulaError::NONE)
274 nGlobalError = nOldGlobalError;
275 if (nGlobalError != FormulaError::NONE)
276 bError = true;
277 break;
278 case svError:
279 PopError();
280 bError = true;
281 break;
282 case svDoubleRef:
283 case svSingleRef:
285 ScAddress aAdr;
286 if (!PopDoubleRefOrSingleRef( aAdr))
287 bError = true;
288 else
291 ScRefCellValue aCell(mrDoc, aAdr);
292 nGlobalError = GetCellErrCode(aCell);
293 if (nGlobalError != FormulaError::NONE)
294 bError = true;
297 break;
298 case svExternalSingleRef:
299 case svExternalDoubleRef:
301 double fVal;
302 svl::SharedString aStr;
303 // Handles also existing jump matrix case and sets error on
304 // elements.
305 GetDoubleOrStringFromMatrix( fVal, aStr);
306 if (nGlobalError != FormulaError::NONE)
307 bError = true;
309 break;
310 case svMatrix:
312 const ScMatrixRef pMat = PopMatrix();
313 if (!pMat || (nGlobalError != FormulaError::NONE && (!bNAonly || nGlobalError == FormulaError::NotAvailable)))
315 bError = true;
316 break; // switch
318 // If the matrix has no queried error at all we can simply use
319 // it as result and don't need to bother with jump matrix.
320 SCSIZE nErrorCol = ::std::numeric_limits<SCSIZE>::max(),
321 nErrorRow = ::std::numeric_limits<SCSIZE>::max();
322 SCSIZE nCols, nRows;
323 pMat->GetDimensions( nCols, nRows );
324 if (nCols == 0 || nRows == 0)
326 bError = true;
327 break; // switch
329 for (SCSIZE nC=0; nC < nCols && !bError; ++nC)
331 for (SCSIZE nR=0; nR < nRows && !bError; ++nR)
333 FormulaError nErr = pMat->GetError( nC, nR );
334 if (nErr != FormulaError::NONE && (!bNAonly || nErr == FormulaError::NotAvailable))
336 bError = true;
337 nErrorCol = nC;
338 nErrorRow = nR;
342 if (!bError)
343 break; // switch, we're done and have the result
345 FormulaConstTokenRef xNew;
346 ScTokenMatrixMap::const_iterator aMapIter;
347 if ((aMapIter = maTokenMatrixMap.find( pCur)) != maTokenMatrixMap.end())
349 xNew = (*aMapIter).second;
351 else
353 const ScMatrix* pMatPtr = pMat.get();
354 std::shared_ptr<ScJumpMatrix> pJumpMat( std::make_shared<ScJumpMatrix>(
355 pCur->GetOpCode(), nCols, nRows));
356 // Init all jumps to no error to save single calls. Error
357 // is the exceptional condition.
358 const double fFlagResult = CreateDoubleError( FormulaError::JumpMatHasResult);
359 pJumpMat->SetAllJumps( fFlagResult, pJump[ nJumpCount ], pJump[ nJumpCount ] );
360 // Up to first error position simply store results, no need
361 // to evaluate error conditions again.
362 SCSIZE nC = 0, nR = 0;
363 for ( ; nC < nCols && (nC != nErrorCol || nR != nErrorRow); /*nop*/ )
365 for (nR = 0 ; nR < nRows && (nC != nErrorCol || nR != nErrorRow); ++nR)
367 lcl_storeJumpMatResult(pMatPtr, pJumpMat.get(), nC, nR);
369 if (nC != nErrorCol && nR != nErrorRow)
370 ++nC;
372 // Now the mixed cases.
373 for ( ; nC < nCols; ++nC)
375 for ( ; nR < nRows; ++nR)
377 FormulaError nErr = pMat->GetError( nC, nR );
378 if (nErr != FormulaError::NONE && (!bNAonly || nErr == FormulaError::NotAvailable))
379 { // TRUE, THEN path
380 pJumpMat->SetJump( nC, nR, 1.0, pJump[ 1 ], pJump[ nJumpCount ] );
382 else
383 { // FALSE, EMPTY path, store result instead
384 lcl_storeJumpMatResult(pMatPtr, pJumpMat.get(), nC, nR);
387 nR = 0;
389 xNew = new ScJumpMatrixToken(std::move(pJumpMat));
390 GetTokenMatrixMap().emplace( pCur, xNew );
392 nGlobalError = nOldGlobalError;
393 PushTokenRef( xNew );
394 // set endpoint of path for main code line
395 aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
396 return;
398 break;
401 if (bError && (!bNAonly || nGlobalError == FormulaError::NotAvailable))
403 // error, calculate 2nd argument
404 nGlobalError = FormulaError::NONE;
405 aCode.Jump( pJump[ 1 ], pJump[ nJumpCount ] );
407 else
409 // no error, push 1st argument and continue
410 nGlobalError = nOldGlobalError;
411 PushTokenRef( xToken);
412 aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
416 void ScInterpreter::ScChooseJump()
418 // We have to set a jump, if there was none chosen because of an error set
419 // it to endpoint.
420 bool bHaveJump = false;
421 const short* pJump = pCur->GetJump();
422 short nJumpCount = pJump[ 0 ];
423 MatrixJumpConditionToMatrix();
424 switch ( GetStackType() )
426 case svMatrix:
428 ScMatrixRef pMat = PopMatrix();
429 if ( !pMat )
430 PushIllegalParameter();
431 else
433 FormulaConstTokenRef xNew;
434 ScTokenMatrixMap::const_iterator aMapIter;
435 // DoubleError handled by JumpMatrix
436 pMat->SetErrorInterpreter( nullptr);
437 SCSIZE nCols, nRows;
438 pMat->GetDimensions( nCols, nRows );
439 if ( nCols == 0 || nRows == 0 )
440 PushIllegalParameter();
441 else if ((aMapIter = maTokenMatrixMap.find(
442 pCur)) != maTokenMatrixMap.end())
443 xNew = (*aMapIter).second;
444 else
446 std::shared_ptr<ScJumpMatrix> pJumpMat( std::make_shared<ScJumpMatrix>(
447 pCur->GetOpCode(), nCols, nRows));
448 for ( SCSIZE nC=0; nC < nCols; ++nC )
450 for ( SCSIZE nR=0; nR < nRows; ++nR )
452 double fVal;
453 bool bIsValue = pMat->IsValue(nC, nR);
454 if ( bIsValue )
456 fVal = pMat->GetDouble(nC, nR);
457 bIsValue = std::isfinite( fVal );
458 if ( bIsValue )
460 fVal = ::rtl::math::approxFloor( fVal);
461 if ( (fVal < 1) || (fVal >= nJumpCount))
463 bIsValue = false;
464 fVal = CreateDoubleError(
465 FormulaError::IllegalArgument);
469 else
471 fVal = CreateDoubleError( FormulaError::NoValue);
473 if ( bIsValue )
475 pJumpMat->SetJump( nC, nR, fVal,
476 pJump[ static_cast<short>(fVal) ],
477 pJump[ nJumpCount ]);
479 else
481 pJumpMat->SetJump( nC, nR, fVal,
482 pJump[ nJumpCount ],
483 pJump[ nJumpCount ]);
487 xNew = new ScJumpMatrixToken(std::move(pJumpMat));
488 GetTokenMatrixMap().emplace(pCur, xNew);
490 if (xNew)
492 PushTokenRef( xNew);
493 // set endpoint of path for main code line
494 aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
495 bHaveJump = true;
499 break;
500 default:
502 sal_Int16 nJumpIndex = GetInt16();
503 if (nGlobalError == FormulaError::NONE && (nJumpIndex >= 1) && (nJumpIndex < nJumpCount))
505 aCode.Jump( pJump[ static_cast<short>(nJumpIndex) ], pJump[ nJumpCount ] );
506 bHaveJump = true;
508 else
509 PushIllegalArgument();
512 if (!bHaveJump)
513 aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
516 static void lcl_AdjustJumpMatrix( ScJumpMatrix* pJumpM, SCSIZE nParmCols, SCSIZE nParmRows )
518 SCSIZE nJumpCols, nJumpRows;
519 SCSIZE nResCols, nResRows;
520 SCSIZE nAdjustCols, nAdjustRows;
521 pJumpM->GetDimensions( nJumpCols, nJumpRows );
522 pJumpM->GetResMatDimensions( nResCols, nResRows );
523 if (!(( nJumpCols == 1 && nParmCols > nResCols ) ||
524 ( nJumpRows == 1 && nParmRows > nResRows )))
525 return;
527 if ( nJumpCols == 1 && nJumpRows == 1 )
529 nAdjustCols = std::max(nParmCols, nResCols);
530 nAdjustRows = std::max(nParmRows, nResRows);
532 else if ( nJumpCols == 1 )
534 nAdjustCols = nParmCols;
535 nAdjustRows = nResRows;
537 else
539 nAdjustCols = nResCols;
540 nAdjustRows = nParmRows;
542 pJumpM->SetNewResMat( nAdjustCols, nAdjustRows );
545 bool ScInterpreter::JumpMatrix( short nStackLevel )
547 pJumpMatrix = pStack[sp-nStackLevel]->GetJumpMatrix();
548 bool bHasResMat = pJumpMatrix->HasResultMatrix();
549 SCSIZE nC, nR;
550 if ( nStackLevel == 2 )
552 if ( aCode.HasStacked() )
553 aCode.Pop(); // pop what Jump() pushed
554 else
556 assert(!"pop goes the weasel");
559 if ( !bHasResMat )
561 Pop();
562 SetError( FormulaError::UnknownStackVariable );
564 else
566 pJumpMatrix->GetPos( nC, nR );
567 switch ( GetStackType() )
569 case svDouble:
571 double fVal = GetDouble();
572 if ( nGlobalError != FormulaError::NONE )
574 fVal = CreateDoubleError( nGlobalError );
575 nGlobalError = FormulaError::NONE;
577 pJumpMatrix->PutResultDouble( fVal, nC, nR );
579 break;
580 case svString:
582 svl::SharedString aStr = GetString();
583 if ( nGlobalError != FormulaError::NONE )
585 pJumpMatrix->PutResultDouble( CreateDoubleError( nGlobalError),
586 nC, nR);
587 nGlobalError = FormulaError::NONE;
589 else
590 pJumpMatrix->PutResultString(aStr, nC, nR);
592 break;
593 case svSingleRef:
595 FormulaConstTokenRef xRef = pStack[sp-1];
596 ScAddress aAdr;
597 PopSingleRef( aAdr );
598 if ( nGlobalError != FormulaError::NONE )
600 pJumpMatrix->PutResultDouble( CreateDoubleError( nGlobalError),
601 nC, nR);
602 nGlobalError = FormulaError::NONE;
604 else
606 ScRefCellValue aCell(mrDoc, aAdr);
607 if (aCell.hasEmptyValue())
608 pJumpMatrix->PutResultEmpty( nC, nR );
609 else if (aCell.hasNumeric())
611 double fVal = GetCellValue(aAdr, aCell);
612 if ( nGlobalError != FormulaError::NONE )
614 fVal = CreateDoubleError(
615 nGlobalError);
616 nGlobalError = FormulaError::NONE;
618 pJumpMatrix->PutResultDouble( fVal, nC, nR );
620 else
622 svl::SharedString aStr;
623 GetCellString(aStr, aCell);
624 if ( nGlobalError != FormulaError::NONE )
626 pJumpMatrix->PutResultDouble( CreateDoubleError(
627 nGlobalError), nC, nR);
628 nGlobalError = FormulaError::NONE;
630 else
631 pJumpMatrix->PutResultString(aStr, nC, nR);
635 formula::ParamClass eReturnType = ScParameterClassification::GetParameterType( pCur, SAL_MAX_UINT16);
636 if (eReturnType == ParamClass::Reference)
638 /* TODO: What about error handling and do we actually
639 * need the result matrix above at all in this case? */
640 ScComplexRefData aRef;
641 aRef.Ref1 = aRef.Ref2 = *(xRef->GetSingleRef());
642 pJumpMatrix->GetRefList().push_back( aRef);
645 break;
646 case svDoubleRef:
647 { // upper left plus offset within matrix
648 FormulaConstTokenRef xRef = pStack[sp-1];
649 double fVal;
650 ScRange aRange;
651 PopDoubleRef( aRange );
652 if ( nGlobalError != FormulaError::NONE )
654 fVal = CreateDoubleError( nGlobalError );
655 nGlobalError = FormulaError::NONE;
656 pJumpMatrix->PutResultDouble( fVal, nC, nR );
658 else
660 // Do not modify the original range because we use it
661 // to adjust the size of the result matrix if necessary.
662 ScAddress aAdr( aRange.aStart);
663 sal_uLong nCol = static_cast<sal_uLong>(aAdr.Col()) + nC;
664 sal_uLong nRow = static_cast<sal_uLong>(aAdr.Row()) + nR;
665 if ((nCol > o3tl::make_unsigned(aRange.aEnd.Col()) &&
666 aRange.aEnd.Col() != aRange.aStart.Col())
667 || (nRow > o3tl::make_unsigned(aRange.aEnd.Row()) &&
668 aRange.aEnd.Row() != aRange.aStart.Row()))
670 fVal = CreateDoubleError( FormulaError::NotAvailable );
671 pJumpMatrix->PutResultDouble( fVal, nC, nR );
673 else
675 // Replicate column and/or row of a vector if it is
676 // one. Note that this could be a range reference
677 // that in fact consists of only one cell, e.g. A1:A1
678 if (aRange.aEnd.Col() == aRange.aStart.Col())
679 nCol = aRange.aStart.Col();
680 if (aRange.aEnd.Row() == aRange.aStart.Row())
681 nRow = aRange.aStart.Row();
682 aAdr.SetCol( static_cast<SCCOL>(nCol) );
683 aAdr.SetRow( static_cast<SCROW>(nRow) );
684 ScRefCellValue aCell(mrDoc, aAdr);
685 if (aCell.hasEmptyValue())
686 pJumpMatrix->PutResultEmpty( nC, nR );
687 else if (aCell.hasNumeric())
689 double fCellVal = GetCellValue(aAdr, aCell);
690 if ( nGlobalError != FormulaError::NONE )
692 fCellVal = CreateDoubleError(
693 nGlobalError);
694 nGlobalError = FormulaError::NONE;
696 pJumpMatrix->PutResultDouble( fCellVal, nC, nR );
698 else
700 svl::SharedString aStr;
701 GetCellString(aStr, aCell);
702 if ( nGlobalError != FormulaError::NONE )
704 pJumpMatrix->PutResultDouble( CreateDoubleError(
705 nGlobalError), nC, nR);
706 nGlobalError = FormulaError::NONE;
708 else
709 pJumpMatrix->PutResultString(aStr, nC, nR);
712 SCSIZE nParmCols = aRange.aEnd.Col() - aRange.aStart.Col() + 1;
713 SCSIZE nParmRows = aRange.aEnd.Row() - aRange.aStart.Row() + 1;
714 lcl_AdjustJumpMatrix( pJumpMatrix, nParmCols, nParmRows );
717 formula::ParamClass eReturnType = ScParameterClassification::GetParameterType( pCur, SAL_MAX_UINT16);
718 if (eReturnType == ParamClass::Reference)
720 /* TODO: What about error handling and do we actually
721 * need the result matrix above at all in this case? */
722 pJumpMatrix->GetRefList().push_back( *(xRef->GetDoubleRef()));
725 break;
726 case svExternalSingleRef:
728 ScExternalRefCache::TokenRef pToken;
729 PopExternalSingleRef(pToken);
730 if (nGlobalError != FormulaError::NONE)
732 pJumpMatrix->PutResultDouble( CreateDoubleError( nGlobalError), nC, nR );
733 nGlobalError = FormulaError::NONE;
735 else
737 switch (pToken->GetType())
739 case svDouble:
740 pJumpMatrix->PutResultDouble( pToken->GetDouble(), nC, nR );
741 break;
742 case svString:
743 pJumpMatrix->PutResultString( pToken->GetString(), nC, nR );
744 break;
745 case svEmptyCell:
746 pJumpMatrix->PutResultEmpty( nC, nR );
747 break;
748 default:
749 // svError was already handled (set by
750 // PopExternalSingleRef()) with nGlobalError
751 // above.
752 assert(!"unhandled svExternalSingleRef case");
753 pJumpMatrix->PutResultDouble( CreateDoubleError(
754 FormulaError::UnknownStackVariable), nC, nR );
758 break;
759 case svExternalDoubleRef:
760 case svMatrix:
761 { // match matrix offsets
762 double fVal;
763 ScMatrixRef pMat = GetMatrix();
764 if ( nGlobalError != FormulaError::NONE )
766 fVal = CreateDoubleError( nGlobalError );
767 nGlobalError = FormulaError::NONE;
768 pJumpMatrix->PutResultDouble( fVal, nC, nR );
770 else if ( !pMat )
772 fVal = CreateDoubleError( FormulaError::UnknownVariable );
773 pJumpMatrix->PutResultDouble( fVal, nC, nR );
775 else
777 SCSIZE nCols, nRows;
778 pMat->GetDimensions( nCols, nRows );
779 if ((nCols <= nC && nCols != 1) ||
780 (nRows <= nR && nRows != 1))
782 fVal = CreateDoubleError( FormulaError::NotAvailable );
783 pJumpMatrix->PutResultDouble( fVal, nC, nR );
785 else
787 // GetMatrix() does SetErrorInterpreter() at the
788 // matrix, do not propagate an error from
789 // matrix->GetValue() as global error.
790 pMat->SetErrorInterpreter(nullptr);
791 lcl_storeJumpMatResult(pMat.get(), pJumpMatrix, nC, nR);
793 lcl_AdjustJumpMatrix( pJumpMatrix, nCols, nRows );
796 break;
797 case svError:
799 PopError();
800 double fVal = CreateDoubleError( nGlobalError);
801 nGlobalError = FormulaError::NONE;
802 pJumpMatrix->PutResultDouble( fVal, nC, nR );
804 break;
805 default:
807 Pop();
808 double fVal = CreateDoubleError( FormulaError::IllegalArgument);
809 pJumpMatrix->PutResultDouble( fVal, nC, nR );
814 bool bCont = pJumpMatrix->Next( nC, nR );
815 if ( bCont )
817 double fBool;
818 short nStart, nNext, nStop;
819 pJumpMatrix->GetJump( nC, nR, fBool, nStart, nNext, nStop );
820 while ( bCont && nStart == nNext )
821 { // push all results that have no jump path
822 if ( bHasResMat && (GetDoubleErrorValue( fBool) != FormulaError::JumpMatHasResult) )
824 // a false without path results in an empty path value
825 if ( fBool == 0.0 )
826 pJumpMatrix->PutResultEmptyPath( nC, nR );
827 else
828 pJumpMatrix->PutResultDouble( fBool, nC, nR );
830 bCont = pJumpMatrix->Next( nC, nR );
831 if ( bCont )
832 pJumpMatrix->GetJump( nC, nR, fBool, nStart, nNext, nStop );
834 if ( bCont && nStart != nNext )
836 const ScTokenVec & rParams = pJumpMatrix->GetJumpParameters();
837 for ( auto const & i : rParams )
839 // This is not the current state of the interpreter, so
840 // push without error, and elements' errors are coded into
841 // double.
842 PushWithoutError(*i);
844 aCode.Jump( nStart, nNext, nStop );
847 if ( !bCont )
848 { // We're done with it, throw away jump matrix, keep result.
849 // For an intermediate result of Reference use the array of references
850 // if there are more than one reference and the current ForceArray
851 // context is ReferenceOrRefArray.
852 // Else (also for a final result of Reference) use the matrix.
853 // Treat the result of a jump command as final and use the matrix (see
854 // tdf#115493 for why).
855 if (pCur->GetInForceArray() == ParamClass::ReferenceOrRefArray &&
856 pJumpMatrix->GetRefList().size() > 1 &&
857 ScParameterClassification::GetParameterType( pCur, SAL_MAX_UINT16) == ParamClass::Reference &&
858 !FormulaCompiler::IsOpCodeJumpCommand( pJumpMatrix->GetOpCode()) &&
859 aCode.PeekNextOperator())
861 FormulaTokenRef xRef = new ScRefListToken(true);
862 *(xRef->GetRefList()) = pJumpMatrix->GetRefList();
863 pJumpMatrix = nullptr;
864 Pop();
865 PushTokenRef( xRef);
866 maTokenMatrixMap.erase( pCur);
867 // There's no result matrix to remember in this case.
869 else
871 ScMatrix* pResMat = pJumpMatrix->GetResultMatrix();
872 pJumpMatrix = nullptr;
873 Pop();
874 PushMatrix( pResMat );
875 // Remove jump matrix from map and remember result matrix in case it
876 // could be reused in another path of the same condition.
877 maTokenMatrixMap.erase( pCur);
878 maTokenMatrixMap.emplace(pCur, pStack[sp-1]);
880 return true;
882 return false;
885 double ScInterpreter::Compare( ScQueryOp eOp )
887 sc::Compare aComp;
888 aComp.meOp = eOp;
889 aComp.mbIgnoreCase = mrDoc.GetDocOptions().IsIgnoreCase();
890 for( short i = 1; i >= 0; i-- )
892 sc::Compare::Cell& rCell = aComp.maCells[i];
894 switch ( GetRawStackType() )
896 case svEmptyCell:
897 Pop();
898 rCell.mbEmpty = true;
899 break;
900 case svMissing:
901 case svDouble:
902 rCell.mfValue = GetDouble();
903 rCell.mbValue = true;
904 break;
905 case svString:
906 rCell.maStr = GetString();
907 rCell.mbValue = false;
908 break;
909 case svDoubleRef :
910 case svSingleRef :
912 ScAddress aAdr;
913 if ( !PopDoubleRefOrSingleRef( aAdr ) )
914 break;
915 ScRefCellValue aCell(mrDoc, aAdr);
916 if (aCell.hasEmptyValue())
917 rCell.mbEmpty = true;
918 else if (aCell.hasString())
920 svl::SharedString aStr;
921 GetCellString(aStr, aCell);
922 rCell.maStr = std::move(aStr);
923 rCell.mbValue = false;
925 else
927 rCell.mfValue = GetCellValue(aAdr, aCell);
928 rCell.mbValue = true;
931 break;
932 case svExternalSingleRef:
934 ScMatrixRef pMat = GetMatrix();
935 if (!pMat)
937 SetError( FormulaError::IllegalParameter);
938 break;
941 SCSIZE nC, nR;
942 pMat->GetDimensions(nC, nR);
943 if (!nC || !nR)
945 SetError( FormulaError::IllegalParameter);
946 break;
948 if (pMat->IsEmpty(0, 0))
949 rCell.mbEmpty = true;
950 else if (pMat->IsStringOrEmpty(0, 0))
952 rCell.maStr = pMat->GetString(0, 0);
953 rCell.mbValue = false;
955 else
957 rCell.mfValue = pMat->GetDouble(0, 0);
958 rCell.mbValue = true;
961 break;
962 case svExternalDoubleRef:
963 // TODO: Find out how to handle this...
964 // Xcl generates a position dependent intersection using
965 // col/row, as it seems to do for all range references, not
966 // only in compare context. We'd need a general implementation
967 // for that behavior similar to svDoubleRef in scalar and array
968 // mode. Which also means we'd have to change all places where
969 // it currently is handled along with svMatrix.
970 default:
971 PopError();
972 SetError( FormulaError::IllegalParameter);
973 break;
976 if( nGlobalError != FormulaError::NONE )
977 return 0;
978 nCurFmtType = nFuncFmtType = SvNumFormatType::LOGICAL;
979 return sc::CompareFunc(aComp);
982 sc::RangeMatrix ScInterpreter::CompareMat( ScQueryOp eOp, sc::CompareOptions* pOptions )
984 sc::Compare aComp;
985 aComp.meOp = eOp;
986 aComp.mbIgnoreCase = mrDoc.GetDocOptions().IsIgnoreCase();
987 sc::RangeMatrix aMat[2];
988 ScAddress aAdr;
989 for( short i = 1; i >= 0; i-- )
991 sc::Compare::Cell& rCell = aComp.maCells[i];
993 switch (GetRawStackType())
995 case svEmptyCell:
996 Pop();
997 rCell.mbEmpty = true;
998 break;
999 case svMissing:
1000 case svDouble:
1001 rCell.mfValue = GetDouble();
1002 rCell.mbValue = true;
1003 break;
1004 case svString:
1005 rCell.maStr = GetString();
1006 rCell.mbValue = false;
1007 break;
1008 case svSingleRef:
1010 PopSingleRef( aAdr );
1011 ScRefCellValue aCell(mrDoc, aAdr);
1012 if (aCell.hasEmptyValue())
1013 rCell.mbEmpty = true;
1014 else if (aCell.hasString())
1016 svl::SharedString aStr;
1017 GetCellString(aStr, aCell);
1018 rCell.maStr = std::move(aStr);
1019 rCell.mbValue = false;
1021 else
1023 rCell.mfValue = GetCellValue(aAdr, aCell);
1024 rCell.mbValue = true;
1027 break;
1028 case svExternalSingleRef:
1029 case svExternalDoubleRef:
1030 case svDoubleRef:
1031 case svMatrix:
1032 aMat[i] = GetRangeMatrix();
1033 if (!aMat[i].mpMat)
1034 SetError( FormulaError::IllegalParameter);
1035 else
1036 aMat[i].mpMat->SetErrorInterpreter(nullptr);
1037 // errors are transported as DoubleError inside matrix
1038 break;
1039 default:
1040 PopError();
1041 SetError( FormulaError::IllegalParameter);
1042 break;
1046 sc::RangeMatrix aRes;
1048 if (nGlobalError != FormulaError::NONE)
1050 nCurFmtType = nFuncFmtType = SvNumFormatType::LOGICAL;
1051 return aRes;
1054 if (aMat[0].mpMat && aMat[1].mpMat)
1056 SCSIZE nC0, nC1;
1057 SCSIZE nR0, nR1;
1058 aMat[0].mpMat->GetDimensions(nC0, nR0);
1059 aMat[1].mpMat->GetDimensions(nC1, nR1);
1060 SCSIZE nC = std::max( nC0, nC1 );
1061 SCSIZE nR = std::max( nR0, nR1 );
1062 aRes.mpMat = GetNewMat( nC, nR, /*bEmpty*/true );
1063 if (!aRes.mpMat)
1064 return aRes;
1065 for ( SCSIZE j=0; j<nC; j++ )
1067 for ( SCSIZE k=0; k<nR; k++ )
1069 SCSIZE nCol = j, nRow = k;
1070 if (aMat[0].mpMat->ValidColRowOrReplicated(nCol, nRow) &&
1071 aMat[1].mpMat->ValidColRowOrReplicated(nCol, nRow))
1073 for ( short i=1; i>=0; i-- )
1075 sc::Compare::Cell& rCell = aComp.maCells[i];
1077 if (aMat[i].mpMat->IsStringOrEmpty(j, k))
1079 rCell.mbValue = false;
1080 rCell.maStr = aMat[i].mpMat->GetString(j, k);
1081 rCell.mbEmpty = aMat[i].mpMat->IsEmpty(j, k);
1083 else
1085 rCell.mbValue = true;
1086 rCell.mfValue = aMat[i].mpMat->GetDouble(j, k);
1087 rCell.mbEmpty = false;
1090 aRes.mpMat->PutDouble( sc::CompareFunc( aComp, pOptions), j, k);
1092 else
1093 aRes.mpMat->PutError( FormulaError::NoValue, j, k);
1097 switch (eOp)
1099 case SC_EQUAL:
1100 aRes.mpMat->CompareEqual();
1101 break;
1102 case SC_LESS:
1103 aRes.mpMat->CompareLess();
1104 break;
1105 case SC_GREATER:
1106 aRes.mpMat->CompareGreater();
1107 break;
1108 case SC_LESS_EQUAL:
1109 aRes.mpMat->CompareLessEqual();
1110 break;
1111 case SC_GREATER_EQUAL:
1112 aRes.mpMat->CompareGreaterEqual();
1113 break;
1114 case SC_NOT_EQUAL:
1115 aRes.mpMat->CompareNotEqual();
1116 break;
1117 default:
1118 SAL_WARN("sc", "ScInterpreter::QueryMat: unhandled comparison operator: " << static_cast<int>(eOp));
1119 aRes.mpMat.reset();
1120 return aRes;
1123 else if (aMat[0].mpMat || aMat[1].mpMat)
1125 size_t i = ( aMat[0].mpMat ? 0 : 1);
1127 aRes.mnCol1 = aMat[i].mnCol1;
1128 aRes.mnRow1 = aMat[i].mnRow1;
1129 aRes.mnTab1 = aMat[i].mnTab1;
1130 aRes.mnCol2 = aMat[i].mnCol2;
1131 aRes.mnRow2 = aMat[i].mnRow2;
1132 aRes.mnTab2 = aMat[i].mnTab2;
1134 ScMatrix& rMat = *aMat[i].mpMat;
1135 aRes.mpMat = rMat.CompareMatrix(aComp, i, pOptions);
1136 if (!aRes.mpMat)
1137 return aRes;
1140 nCurFmtType = nFuncFmtType = SvNumFormatType::LOGICAL;
1141 return aRes;
1144 ScMatrixRef ScInterpreter::QueryMat( const ScMatrixRef& pMat, sc::CompareOptions& rOptions )
1146 SvNumFormatType nSaveCurFmtType = nCurFmtType;
1147 SvNumFormatType nSaveFuncFmtType = nFuncFmtType;
1148 PushMatrix( pMat);
1149 const ScQueryEntry::Item& rItem = rOptions.aQueryEntry.GetQueryItem();
1150 if (rItem.meType == ScQueryEntry::ByString)
1151 PushString(rItem.maString.getString());
1152 else
1153 PushDouble(rItem.mfVal);
1154 ScMatrixRef pResultMatrix = CompareMat(rOptions.aQueryEntry.eOp, &rOptions).mpMat;
1155 nCurFmtType = nSaveCurFmtType;
1156 nFuncFmtType = nSaveFuncFmtType;
1157 if (nGlobalError != FormulaError::NONE || !pResultMatrix)
1159 SetError( FormulaError::IllegalParameter);
1160 return pResultMatrix;
1163 return pResultMatrix;
1166 void ScInterpreter::ScEqual()
1168 if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix )
1170 sc::RangeMatrix aMat = CompareMat(SC_EQUAL);
1171 if (!aMat.mpMat)
1173 PushIllegalParameter();
1174 return;
1177 PushMatrix(aMat);
1179 else
1180 PushInt( int(Compare( SC_EQUAL) == 0) );
1183 void ScInterpreter::ScNotEqual()
1185 if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix )
1187 sc::RangeMatrix aMat = CompareMat(SC_NOT_EQUAL);
1188 if (!aMat.mpMat)
1190 PushIllegalParameter();
1191 return;
1194 PushMatrix(aMat);
1196 else
1197 PushInt( int(Compare( SC_NOT_EQUAL) != 0) );
1200 void ScInterpreter::ScLess()
1202 if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix )
1204 sc::RangeMatrix aMat = CompareMat(SC_LESS);
1205 if (!aMat.mpMat)
1207 PushIllegalParameter();
1208 return;
1211 PushMatrix(aMat);
1213 else
1214 PushInt( int(Compare( SC_LESS) < 0) );
1217 void ScInterpreter::ScGreater()
1219 if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix )
1221 sc::RangeMatrix aMat = CompareMat(SC_GREATER);
1222 if (!aMat.mpMat)
1224 PushIllegalParameter();
1225 return;
1228 PushMatrix(aMat);
1230 else
1231 PushInt( int(Compare( SC_GREATER) > 0) );
1234 void ScInterpreter::ScLessEqual()
1236 if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix )
1238 sc::RangeMatrix aMat = CompareMat(SC_LESS_EQUAL);
1239 if (!aMat.mpMat)
1241 PushIllegalParameter();
1242 return;
1245 PushMatrix(aMat);
1247 else
1248 PushInt( int(Compare( SC_LESS_EQUAL) <= 0) );
1251 void ScInterpreter::ScGreaterEqual()
1253 if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix )
1255 sc::RangeMatrix aMat = CompareMat(SC_GREATER_EQUAL);
1256 if (!aMat.mpMat)
1258 PushIllegalParameter();
1259 return;
1262 PushMatrix(aMat);
1264 else
1265 PushInt( int(Compare( SC_GREATER_EQUAL) >= 0) );
1268 void ScInterpreter::ScAnd()
1270 nFuncFmtType = SvNumFormatType::LOGICAL;
1271 short nParamCount = GetByte();
1272 if ( !MustHaveParamCountMin( nParamCount, 1 ) )
1273 return;
1275 bool bHaveValue = false;
1276 bool bRes = true;
1277 size_t nRefInList = 0;
1278 while( nParamCount-- > 0)
1280 if ( nGlobalError == FormulaError::NONE )
1282 switch ( GetStackType() )
1284 case svDouble :
1285 bHaveValue = true;
1286 bRes &= ( PopDouble() != 0.0 );
1287 break;
1288 case svString :
1289 Pop();
1290 SetError( FormulaError::NoValue );
1291 break;
1292 case svSingleRef :
1294 ScAddress aAdr;
1295 PopSingleRef( aAdr );
1296 if ( nGlobalError == FormulaError::NONE )
1298 ScRefCellValue aCell(mrDoc, aAdr);
1299 if (aCell.hasNumeric())
1301 bHaveValue = true;
1302 bRes &= ( GetCellValue(aAdr, aCell) != 0.0 );
1304 // else: Xcl raises no error here
1307 break;
1308 case svDoubleRef:
1309 case svRefList:
1311 ScRange aRange;
1312 PopDoubleRef( aRange, nParamCount, nRefInList);
1313 if ( nGlobalError == FormulaError::NONE )
1315 double fVal;
1316 FormulaError nErr = FormulaError::NONE;
1317 ScValueIterator aValIter( mrContext, aRange );
1318 if ( aValIter.GetFirst( fVal, nErr ) && nErr == FormulaError::NONE )
1320 bHaveValue = true;
1323 bRes &= ( fVal != 0.0 );
1324 } while ( (nErr == FormulaError::NONE) &&
1325 aValIter.GetNext( fVal, nErr ) );
1327 SetError( nErr );
1330 break;
1331 case svExternalSingleRef:
1332 case svExternalDoubleRef:
1333 case svMatrix:
1335 ScMatrixRef pMat = GetMatrix();
1336 if ( pMat )
1338 bHaveValue = true;
1339 double fVal = pMat->And();
1340 FormulaError nErr = GetDoubleErrorValue( fVal );
1341 if ( nErr != FormulaError::NONE )
1343 SetError( nErr );
1344 bRes = false;
1346 else
1347 bRes &= (fVal != 0.0);
1349 // else: GetMatrix did set FormulaError::IllegalParameter
1351 break;
1352 default:
1353 PopError();
1354 SetError( FormulaError::IllegalParameter);
1357 else
1358 Pop();
1360 if ( bHaveValue )
1361 PushInt( int(bRes) );
1362 else
1363 PushNoValue();
1366 void ScInterpreter::ScOr()
1368 nFuncFmtType = SvNumFormatType::LOGICAL;
1369 short nParamCount = GetByte();
1370 if ( !MustHaveParamCountMin( nParamCount, 1 ) )
1371 return;
1373 bool bHaveValue = false;
1374 bool bRes = false;
1375 size_t nRefInList = 0;
1376 while( nParamCount-- > 0)
1378 if ( nGlobalError == FormulaError::NONE )
1380 switch ( GetStackType() )
1382 case svDouble :
1383 bHaveValue = true;
1384 bRes |= ( PopDouble() != 0.0 );
1385 break;
1386 case svString :
1387 Pop();
1388 SetError( FormulaError::NoValue );
1389 break;
1390 case svSingleRef :
1392 ScAddress aAdr;
1393 PopSingleRef( aAdr );
1394 if ( nGlobalError == FormulaError::NONE )
1396 ScRefCellValue aCell(mrDoc, aAdr);
1397 if (aCell.hasNumeric())
1399 bHaveValue = true;
1400 bRes |= ( GetCellValue(aAdr, aCell) != 0.0 );
1402 // else: Xcl raises no error here
1405 break;
1406 case svDoubleRef:
1407 case svRefList:
1409 ScRange aRange;
1410 PopDoubleRef( aRange, nParamCount, nRefInList);
1411 if ( nGlobalError == FormulaError::NONE )
1413 double fVal;
1414 FormulaError nErr = FormulaError::NONE;
1415 ScValueIterator aValIter( mrContext, aRange );
1416 if ( aValIter.GetFirst( fVal, nErr ) )
1418 bHaveValue = true;
1421 bRes |= ( fVal != 0.0 );
1422 } while ( (nErr == FormulaError::NONE) &&
1423 aValIter.GetNext( fVal, nErr ) );
1425 SetError( nErr );
1428 break;
1429 case svExternalSingleRef:
1430 case svExternalDoubleRef:
1431 case svMatrix:
1433 bHaveValue = true;
1434 ScMatrixRef pMat = GetMatrix();
1435 if ( pMat )
1437 bHaveValue = true;
1438 double fVal = pMat->Or();
1439 FormulaError nErr = GetDoubleErrorValue( fVal );
1440 if ( nErr != FormulaError::NONE )
1442 SetError( nErr );
1443 bRes = false;
1445 else
1446 bRes |= (fVal != 0.0);
1448 // else: GetMatrix did set FormulaError::IllegalParameter
1450 break;
1451 default:
1452 PopError();
1453 SetError( FormulaError::IllegalParameter);
1456 else
1457 Pop();
1459 if ( bHaveValue )
1460 PushInt( int(bRes) );
1461 else
1462 PushNoValue();
1465 void ScInterpreter::ScXor()
1468 nFuncFmtType = SvNumFormatType::LOGICAL;
1469 short nParamCount = GetByte();
1470 if ( !MustHaveParamCountMin( nParamCount, 1 ) )
1471 return;
1473 bool bHaveValue = false;
1474 bool bRes = false;
1475 size_t nRefInList = 0;
1476 while( nParamCount-- > 0)
1478 if ( nGlobalError == FormulaError::NONE )
1480 switch ( GetStackType() )
1482 case svDouble :
1483 bHaveValue = true;
1484 bRes ^= ( PopDouble() != 0.0 );
1485 break;
1486 case svString :
1487 Pop();
1488 SetError( FormulaError::NoValue );
1489 break;
1490 case svSingleRef :
1492 ScAddress aAdr;
1493 PopSingleRef( aAdr );
1494 if ( nGlobalError == FormulaError::NONE )
1496 ScRefCellValue aCell(mrDoc, aAdr);
1497 if (aCell.hasNumeric())
1499 bHaveValue = true;
1500 bRes ^= ( GetCellValue(aAdr, aCell) != 0.0 );
1502 /* TODO: set error? Excel doesn't have XOR, but
1503 * doesn't set an error in this case for AND and
1504 * OR. */
1507 break;
1508 case svDoubleRef:
1509 case svRefList:
1511 ScRange aRange;
1512 PopDoubleRef( aRange, nParamCount, nRefInList);
1513 if ( nGlobalError == FormulaError::NONE )
1515 double fVal;
1516 FormulaError nErr = FormulaError::NONE;
1517 ScValueIterator aValIter( mrContext, aRange );
1518 if ( aValIter.GetFirst( fVal, nErr ) )
1520 bHaveValue = true;
1523 bRes ^= ( fVal != 0.0 );
1524 } while ( (nErr == FormulaError::NONE) &&
1525 aValIter.GetNext( fVal, nErr ) );
1527 SetError( nErr );
1530 break;
1531 case svExternalSingleRef:
1532 case svExternalDoubleRef:
1533 case svMatrix:
1535 bHaveValue = true;
1536 ScMatrixRef pMat = GetMatrix();
1537 if ( pMat )
1539 bHaveValue = true;
1540 double fVal = pMat->Xor();
1541 FormulaError nErr = GetDoubleErrorValue( fVal );
1542 if ( nErr != FormulaError::NONE )
1544 SetError( nErr );
1545 bRes = false;
1547 else
1548 bRes ^= ( fVal != 0.0 );
1550 // else: GetMatrix did set FormulaError::IllegalParameter
1552 break;
1553 default:
1554 PopError();
1555 SetError( FormulaError::IllegalParameter);
1558 else
1559 Pop();
1561 if ( bHaveValue )
1562 PushInt( int(bRes) );
1563 else
1564 PushNoValue();
1567 void ScInterpreter::ScNeg()
1569 // Simple negation doesn't change current format type to number, keep
1570 // current type.
1571 nFuncFmtType = nCurFmtType;
1572 switch ( GetStackType() )
1574 case svMatrix :
1576 ScMatrixRef pMat = GetMatrix();
1577 if ( !pMat )
1578 PushIllegalParameter();
1579 else
1581 SCSIZE nC, nR;
1582 pMat->GetDimensions( nC, nR );
1583 ScMatrixRef pResMat = GetNewMat( nC, nR, /*bEmpty*/true );
1584 if ( !pResMat )
1585 PushIllegalArgument();
1586 else
1588 pMat->NegOp( *pResMat);
1589 PushMatrix( pResMat );
1593 break;
1594 default:
1595 PushDouble( -GetDouble() );
1599 void ScInterpreter::ScPercentSign()
1601 nFuncFmtType = SvNumFormatType::PERCENT;
1602 const FormulaToken* pSaveCur = pCur;
1603 sal_uInt8 nSavePar = cPar;
1604 PushInt( 100 );
1605 cPar = 2;
1606 FormulaByteToken aDivOp( ocDiv, cPar );
1607 pCur = &aDivOp;
1608 ScDiv();
1609 pCur = pSaveCur;
1610 cPar = nSavePar;
1613 void ScInterpreter::ScNot()
1615 nFuncFmtType = SvNumFormatType::LOGICAL;
1616 switch ( GetStackType() )
1618 case svMatrix :
1620 ScMatrixRef pMat = GetMatrix();
1621 if ( !pMat )
1622 PushIllegalParameter();
1623 else
1625 SCSIZE nC, nR;
1626 pMat->GetDimensions( nC, nR );
1627 ScMatrixRef pResMat = GetNewMat( nC, nR, /*bEmpty*/true);
1628 if ( !pResMat )
1629 PushIllegalArgument();
1630 else
1632 pMat->NotOp( *pResMat);
1633 PushMatrix( pResMat );
1637 break;
1638 default:
1639 PushInt( int(GetDouble() == 0.0) );
1643 void ScInterpreter::ScBitAnd()
1646 if ( !MustHaveParamCount( GetByte(), 2 ) )
1647 return;
1649 double num1 = ::rtl::math::approxFloor( GetDouble());
1650 double num2 = ::rtl::math::approxFloor( GetDouble());
1651 if ( (num1 >= n2power48) || (num1 < 0) ||
1652 (num2 >= n2power48) || (num2 < 0))
1653 PushIllegalArgument();
1654 else
1655 PushDouble (static_cast<sal_uInt64>(num1) & static_cast<sal_uInt64>(num2));
1658 void ScInterpreter::ScBitOr()
1661 if ( !MustHaveParamCount( GetByte(), 2 ) )
1662 return;
1664 double num1 = ::rtl::math::approxFloor( GetDouble());
1665 double num2 = ::rtl::math::approxFloor( GetDouble());
1666 if ( (num1 >= n2power48) || (num1 < 0) ||
1667 (num2 >= n2power48) || (num2 < 0))
1668 PushIllegalArgument();
1669 else
1670 PushDouble (static_cast<sal_uInt64>(num1) | static_cast<sal_uInt64>(num2));
1673 void ScInterpreter::ScBitXor()
1676 if ( !MustHaveParamCount( GetByte(), 2 ) )
1677 return;
1679 double num1 = ::rtl::math::approxFloor( GetDouble());
1680 double num2 = ::rtl::math::approxFloor( GetDouble());
1681 if ( (num1 >= n2power48) || (num1 < 0) ||
1682 (num2 >= n2power48) || (num2 < 0))
1683 PushIllegalArgument();
1684 else
1685 PushDouble (static_cast<sal_uInt64>(num1) ^ static_cast<sal_uInt64>(num2));
1688 void ScInterpreter::ScBitLshift()
1691 if ( !MustHaveParamCount( GetByte(), 2 ) )
1692 return;
1694 double fShift = ::rtl::math::approxFloor( GetDouble());
1695 double num = ::rtl::math::approxFloor( GetDouble());
1696 if ((num >= n2power48) || (num < 0))
1697 PushIllegalArgument();
1698 else
1700 double fRes;
1701 if (fShift < 0)
1702 fRes = ::rtl::math::approxFloor( num / pow( 2.0, -fShift));
1703 else if (fShift == 0)
1704 fRes = num;
1705 else
1706 fRes = num * pow( 2.0, fShift);
1707 PushDouble( fRes);
1711 void ScInterpreter::ScBitRshift()
1714 if ( !MustHaveParamCount( GetByte(), 2 ) )
1715 return;
1717 double fShift = ::rtl::math::approxFloor( GetDouble());
1718 double num = ::rtl::math::approxFloor( GetDouble());
1719 if ((num >= n2power48) || (num < 0))
1720 PushIllegalArgument();
1721 else
1723 double fRes;
1724 if (fShift < 0)
1725 fRes = num * pow( 2.0, -fShift);
1726 else if (fShift == 0)
1727 fRes = num;
1728 else
1729 fRes = ::rtl::math::approxFloor( num / pow( 2.0, fShift));
1730 PushDouble( fRes);
1734 void ScInterpreter::ScPi()
1736 PushDouble(M_PI);
1739 void ScInterpreter::ScRandomImpl( const std::function<double( double fFirst, double fLast )>& RandomFunc,
1740 double fFirst, double fLast )
1742 if (bMatrixFormula)
1744 SCCOL nCols = 0;
1745 SCROW nRows = 0;
1746 // In JumpMatrix context use its dimensions for the return matrix; the
1747 // formula cell range selected may differ, for example if the result is
1748 // to be transposed.
1749 if (GetStackType(1) == svJumpMatrix)
1751 SCSIZE nC, nR;
1752 pStack[sp-1]->GetJumpMatrix()->GetDimensions( nC, nR);
1753 nCols = std::max<SCCOL>(0, static_cast<SCCOL>(nC));
1754 nRows = std::max<SCROW>(0, static_cast<SCROW>(nR));
1756 else if (pMyFormulaCell)
1757 pMyFormulaCell->GetMatColsRows( nCols, nRows);
1759 if (nCols == 1 && nRows == 1)
1761 // For compatibility with existing
1762 // com.sun.star.sheet.FunctionAccess.callFunction() calls that per
1763 // default are executed in array context unless
1764 // FA.setPropertyValue("IsArrayFunction",False) was set, return a
1765 // scalar double instead of a 1x1 matrix object. tdf#128218
1766 PushDouble( RandomFunc( fFirst, fLast));
1767 return;
1770 // ScViewFunc::EnterMatrix() might be asking for
1771 // ScFormulaCell::GetResultDimensions(), which here are none so create
1772 // a 1x1 matrix at least which exactly is the case when EnterMatrix()
1773 // asks for a not selected range.
1774 if (nCols == 0)
1775 nCols = 1;
1776 if (nRows == 0)
1777 nRows = 1;
1778 ScMatrixRef pResMat = GetNewMat( static_cast<SCSIZE>(nCols), static_cast<SCSIZE>(nRows), /*bEmpty*/true );
1779 if (!pResMat)
1780 PushError( FormulaError::MatrixSize);
1781 else
1783 for (SCCOL i=0; i < nCols; ++i)
1785 for (SCROW j=0; j < nRows; ++j)
1787 pResMat->PutDouble( RandomFunc( fFirst, fLast),
1788 static_cast<SCSIZE>(i), static_cast<SCSIZE>(j));
1791 PushMatrix( pResMat);
1794 else
1796 PushDouble( RandomFunc( fFirst, fLast));
1800 void ScInterpreter::ScRandom()
1802 auto RandomFunc = [this]( double, double )
1804 std::uniform_real_distribution<double> dist(0.0, 1.0);
1805 return dist(mrContext.aRNG);
1807 ScRandomImpl( RandomFunc, 0.0, 0.0 );
1810 void ScInterpreter::ScRandArray()
1812 sal_uInt8 nParamCount = GetByte();
1813 // optional 5th para:
1814 // TRUE for a whole number
1815 // FALSE for a decimal number - default.
1816 bool bWholeNumber = false;
1817 if (nParamCount == 5)
1818 bWholeNumber = GetBoolWithDefault(false);
1820 // optional 4th para: The maximum value of the random numbers
1821 double fMax = 1.0;
1822 if (nParamCount >= 4)
1823 fMax = GetDoubleWithDefault(1.0);
1825 // optional 3rd para: The minimum value of the random numbers
1826 double fMin = 0.0;
1827 if (nParamCount >= 3)
1828 fMin = GetDoubleWithDefault(0.0);
1830 // optional 2nd para: The number of columns of the return array
1831 SCCOL nCols = 1;
1832 if (nParamCount >= 2)
1833 nCols = static_cast<SCCOL>(GetInt32WithDefault(1));
1835 // optional 1st para: The number of rows of the return array
1836 SCROW nRows = 1;
1837 if (nParamCount >= 1)
1838 nRows = static_cast<SCROW>(GetInt32WithDefault(1));
1840 if (bWholeNumber)
1842 fMax = rtl::math::round(fMax, 0, rtl_math_RoundingMode_Up);
1843 fMin = rtl::math::round(fMin, 0, rtl_math_RoundingMode_Up);
1846 if (nGlobalError != FormulaError::NONE || fMin > fMax || nCols <= 0 || nRows <= 0)
1848 PushIllegalArgument();
1849 return;
1852 if (bWholeNumber)
1853 fMax = std::nextafter(fMax + 1, -DBL_MAX);
1854 else
1855 fMax = std::nextafter(fMax, DBL_MAX);
1857 auto RandomFunc = [this](double fFirst, double fLast, bool bWholeNum)
1859 std::uniform_real_distribution<double> dist(fFirst, fLast);
1860 if (bWholeNum)
1861 return floor(dist(mrContext.aRNG));
1862 else
1863 return dist(mrContext.aRNG);
1866 if (nCols == 1 && nRows == 1)
1868 PushDouble(RandomFunc(fMin, fMax, bWholeNumber));
1869 return;
1872 ScMatrixRef pResMat = GetNewMat(static_cast<SCSIZE>(nCols), static_cast<SCSIZE>(nRows), /*bEmpty*/true);
1873 if (!pResMat)
1874 PushError(FormulaError::MatrixSize);
1875 else
1877 for (SCCOL i = 0; i < nCols; ++i)
1879 for (SCROW j = 0; j < nRows; ++j)
1881 pResMat->PutDouble(RandomFunc(fMin, fMax, bWholeNumber),
1882 static_cast<SCSIZE>(i), static_cast<SCSIZE>(j));
1885 PushMatrix(pResMat);
1889 void ScInterpreter::ScRandbetween()
1891 if (!MustHaveParamCount( GetByte(), 2))
1892 return;
1894 // Same like scaddins/source/analysis/analysis.cxx
1895 // AnalysisAddIn::getRandbetween()
1896 double fMax = rtl::math::round( GetDouble(), 0, rtl_math_RoundingMode_Up);
1897 double fMin = rtl::math::round( GetDouble(), 0, rtl_math_RoundingMode_Up);
1898 if (nGlobalError != FormulaError::NONE || fMin > fMax)
1900 PushIllegalArgument();
1901 return;
1903 fMax = std::nextafter( fMax+1, -DBL_MAX);
1904 auto RandomFunc = [this]( double fFirst, double fLast )
1906 std::uniform_real_distribution<double> dist(fFirst, fLast);
1907 return floor(dist(mrContext.aRNG));
1909 ScRandomImpl( RandomFunc, fMin, fMax);
1912 void ScInterpreter::ScTrue()
1914 nFuncFmtType = SvNumFormatType::LOGICAL;
1915 PushInt(1);
1918 void ScInterpreter::ScFalse()
1920 nFuncFmtType = SvNumFormatType::LOGICAL;
1921 PushInt(0);
1924 void ScInterpreter::ScDeg()
1926 PushDouble(basegfx::rad2deg(GetDouble()));
1929 void ScInterpreter::ScRad()
1931 PushDouble(basegfx::deg2rad(GetDouble()));
1934 void ScInterpreter::ScSin()
1936 PushDouble(::rtl::math::sin(GetDouble()));
1939 void ScInterpreter::ScCos()
1941 PushDouble(::rtl::math::cos(GetDouble()));
1944 void ScInterpreter::ScTan()
1946 PushDouble(::rtl::math::tan(GetDouble()));
1949 void ScInterpreter::ScCot()
1951 PushDouble(1.0 / ::rtl::math::tan(GetDouble()));
1954 void ScInterpreter::ScArcSin()
1956 PushDouble(asin(GetDouble()));
1959 void ScInterpreter::ScArcCos()
1961 PushDouble(acos(GetDouble()));
1964 void ScInterpreter::ScArcTan()
1966 PushDouble(atan(GetDouble()));
1969 void ScInterpreter::ScArcCot()
1971 PushDouble((M_PI_2) - atan(GetDouble()));
1974 void ScInterpreter::ScSinHyp()
1976 PushDouble(sinh(GetDouble()));
1979 void ScInterpreter::ScCosHyp()
1981 PushDouble(cosh(GetDouble()));
1984 void ScInterpreter::ScTanHyp()
1986 PushDouble(tanh(GetDouble()));
1989 void ScInterpreter::ScCotHyp()
1991 PushDouble(1.0 / tanh(GetDouble()));
1994 void ScInterpreter::ScArcSinHyp()
1996 PushDouble( ::rtl::math::asinh( GetDouble()));
1999 void ScInterpreter::ScArcCosHyp()
2001 double fVal = GetDouble();
2002 if (fVal < 1.0)
2003 PushIllegalArgument();
2004 else
2005 PushDouble( ::rtl::math::acosh( fVal));
2008 void ScInterpreter::ScArcTanHyp()
2010 double fVal = GetDouble();
2011 if (fabs(fVal) >= 1.0)
2012 PushIllegalArgument();
2013 else
2014 PushDouble(::atanh(fVal));
2017 void ScInterpreter::ScArcCotHyp()
2019 double nVal = GetDouble();
2020 if (fabs(nVal) <= 1.0)
2021 PushIllegalArgument();
2022 else
2023 PushDouble(0.5 * log((nVal + 1.0) / (nVal - 1.0)));
2026 void ScInterpreter::ScCosecant()
2028 PushDouble(1.0 / ::rtl::math::sin(GetDouble()));
2031 void ScInterpreter::ScSecant()
2033 PushDouble(1.0 / ::rtl::math::cos(GetDouble()));
2036 void ScInterpreter::ScCosecantHyp()
2038 PushDouble(1.0 / sinh(GetDouble()));
2041 void ScInterpreter::ScSecantHyp()
2043 PushDouble(1.0 / cosh(GetDouble()));
2046 void ScInterpreter::ScExp()
2048 PushDouble(exp(GetDouble()));
2051 void ScInterpreter::ScSqrt()
2053 double fVal = GetDouble();
2054 if (fVal >= 0.0)
2055 PushDouble(sqrt(fVal));
2056 else
2057 PushIllegalArgument();
2060 void ScInterpreter::ScIsEmpty()
2062 short nRes = 0;
2063 nFuncFmtType = SvNumFormatType::LOGICAL;
2064 switch ( GetRawStackType() )
2066 case svEmptyCell:
2068 FormulaConstTokenRef p = PopToken();
2069 if (!static_cast<const ScEmptyCellToken*>(p.get())->IsInherited())
2070 nRes = 1;
2072 break;
2073 case svDoubleRef :
2074 case svSingleRef :
2076 ScAddress aAdr;
2077 if ( !PopDoubleRefOrSingleRef( aAdr ) )
2078 break;
2079 // NOTE: this differs from COUNTBLANK() ScCountEmptyCells() that
2080 // may treat ="" in the referenced cell as blank for Excel
2081 // interoperability.
2082 ScRefCellValue aCell(mrDoc, aAdr);
2083 if (aCell.getType() == CELLTYPE_NONE)
2084 nRes = 1;
2086 break;
2087 case svExternalSingleRef:
2088 case svExternalDoubleRef:
2089 case svMatrix:
2091 ScMatrixRef pMat = GetMatrix();
2092 if ( !pMat )
2093 ; // nothing
2094 else if ( !pJumpMatrix )
2095 nRes = pMat->IsEmptyCell( 0, 0) ? 1 : 0;
2096 else
2098 SCSIZE nCols, nRows, nC, nR;
2099 pMat->GetDimensions( nCols, nRows);
2100 pJumpMatrix->GetPos( nC, nR);
2101 if ( nC < nCols && nR < nRows )
2102 nRes = pMat->IsEmptyCell( nC, nR) ? 1 : 0;
2103 // else: false, not empty (which is what Xcl does)
2106 break;
2107 default:
2108 Pop();
2110 nGlobalError = FormulaError::NONE;
2111 PushInt( nRes );
2114 bool ScInterpreter::IsString()
2116 nFuncFmtType = SvNumFormatType::LOGICAL;
2117 bool bRes = false;
2118 switch ( GetRawStackType() )
2120 case svString:
2121 Pop();
2122 bRes = true;
2123 break;
2124 case svDoubleRef :
2125 case svSingleRef :
2127 ScAddress aAdr;
2128 if ( !PopDoubleRefOrSingleRef( aAdr ) )
2129 break;
2131 ScRefCellValue aCell(mrDoc, aAdr);
2132 if (GetCellErrCode(aCell) == FormulaError::NONE)
2134 switch (aCell.getType())
2136 case CELLTYPE_STRING :
2137 case CELLTYPE_EDIT :
2138 bRes = true;
2139 break;
2140 case CELLTYPE_FORMULA :
2141 bRes = (!aCell.getFormula()->IsValue() && !aCell.getFormula()->IsEmpty());
2142 break;
2143 default:
2144 ; // nothing
2148 break;
2149 case svExternalSingleRef:
2151 ScExternalRefCache::TokenRef pToken;
2152 PopExternalSingleRef(pToken);
2153 if (nGlobalError == FormulaError::NONE && pToken->GetType() == svString)
2154 bRes = true;
2156 break;
2157 case svExternalDoubleRef:
2158 case svMatrix:
2160 ScMatrixRef pMat = GetMatrix();
2161 if ( !pMat )
2162 ; // nothing
2163 else if ( !pJumpMatrix )
2164 bRes = pMat->IsStringOrEmpty(0, 0) && !pMat->IsEmpty(0, 0);
2165 else
2167 SCSIZE nCols, nRows, nC, nR;
2168 pMat->GetDimensions( nCols, nRows);
2169 pJumpMatrix->GetPos( nC, nR);
2170 if ( nC < nCols && nR < nRows )
2171 bRes = pMat->IsStringOrEmpty( nC, nR) && !pMat->IsEmpty( nC, nR);
2174 break;
2175 default:
2176 Pop();
2178 nGlobalError = FormulaError::NONE;
2179 return bRes;
2182 void ScInterpreter::ScIsString()
2184 PushInt( int(IsString()) );
2187 void ScInterpreter::ScIsNonString()
2189 PushInt( int(!IsString()) );
2192 void ScInterpreter::ScIsLogical()
2194 bool bRes = false;
2195 switch ( GetStackType() )
2197 case svDoubleRef :
2198 case svSingleRef :
2200 ScAddress aAdr;
2201 if ( !PopDoubleRefOrSingleRef( aAdr ) )
2202 break;
2204 ScRefCellValue aCell(mrDoc, aAdr);
2205 if (GetCellErrCode(aCell) == FormulaError::NONE)
2207 if (aCell.hasNumeric())
2209 sal_uInt32 nFormat = GetCellNumberFormat(aAdr, aCell);
2210 bRes = (mrContext.NFGetType(nFormat) == SvNumFormatType::LOGICAL);
2214 break;
2215 case svMatrix:
2217 double fVal;
2218 svl::SharedString aStr;
2219 ScMatValType nMatValType = GetDoubleOrStringFromMatrix( fVal, aStr);
2220 bRes = (nMatValType == ScMatValType::Boolean);
2222 break;
2223 default:
2224 PopError();
2225 if ( nGlobalError == FormulaError::NONE )
2226 bRes = ( nCurFmtType == SvNumFormatType::LOGICAL );
2228 nCurFmtType = nFuncFmtType = SvNumFormatType::LOGICAL;
2229 nGlobalError = FormulaError::NONE;
2230 PushInt( int(bRes) );
2233 void ScInterpreter::ScType()
2235 short nType = 0;
2236 switch ( GetStackType() )
2238 case svDoubleRef :
2239 case svSingleRef :
2241 ScAddress aAdr;
2242 if ( !PopDoubleRefOrSingleRef( aAdr ) )
2243 break;
2245 ScRefCellValue aCell(mrDoc, aAdr);
2246 if (GetCellErrCode(aCell) == FormulaError::NONE)
2248 switch (aCell.getType())
2250 // NOTE: this is Xcl nonsense!
2251 case CELLTYPE_STRING :
2252 case CELLTYPE_EDIT :
2253 nType = 2;
2254 break;
2255 case CELLTYPE_VALUE :
2257 sal_uInt32 nFormat = GetCellNumberFormat(aAdr, aCell);
2258 if (mrContext.NFGetType(nFormat) == SvNumFormatType::LOGICAL)
2259 nType = 4;
2260 else
2261 nType = 1;
2263 break;
2264 case CELLTYPE_NONE:
2265 // always 1, s. tdf#73078
2266 nType = 1;
2267 break;
2268 case CELLTYPE_FORMULA :
2269 nType = 8;
2270 break;
2271 default:
2272 PushIllegalArgument();
2275 else
2276 nType = 16;
2278 break;
2279 case svString:
2280 PopError();
2281 if ( nGlobalError != FormulaError::NONE )
2283 nType = 16;
2284 nGlobalError = FormulaError::NONE;
2286 else
2287 nType = 2;
2288 break;
2289 case svMatrix:
2290 PopMatrix();
2291 if ( nGlobalError != FormulaError::NONE )
2293 nType = 16;
2294 nGlobalError = FormulaError::NONE;
2296 else
2297 nType = 64;
2298 // we could return the type of one element if in JumpMatrix or
2299 // ForceArray mode, but Xcl doesn't ...
2300 break;
2301 default:
2302 PopError();
2303 if ( nGlobalError != FormulaError::NONE )
2305 nType = 16;
2306 nGlobalError = FormulaError::NONE;
2308 else
2309 nType = 1;
2311 PushInt( nType );
2314 static bool lcl_FormatHasNegColor( const SvNumberformat* pFormat )
2316 return pFormat && pFormat->GetColor( 1 );
2319 static bool lcl_FormatHasOpenPar( const SvNumberformat* pFormat )
2321 return pFormat && (pFormat->GetFormatstring().indexOf('(') != -1);
2324 namespace {
2326 void getFormatString(const ScInterpreterContext& rContext, sal_uLong nFormat, OUString& rFmtStr)
2328 rFmtStr = rContext.NFGetCalcCellReturn(nFormat);
2333 void ScInterpreter::ScCell()
2334 { // ATTRIBUTE ; [REF]
2335 sal_uInt8 nParamCount = GetByte();
2336 if( !MustHaveParamCount( nParamCount, 1, 2 ) )
2337 return;
2339 ScAddress aCellPos( aPos );
2340 if( nParamCount == 2 )
2342 switch (GetStackType())
2344 case svExternalSingleRef:
2345 case svExternalDoubleRef:
2347 // Let's handle external reference separately...
2348 ScCellExternal();
2349 return;
2351 case svDoubleRef:
2353 // Exceptionally not an intersecting position but top left.
2354 // See ODF v1.3 part 4 OpenFormula 6.13.3 CELL
2355 ScRange aRange;
2356 PopDoubleRef( aRange);
2357 aCellPos = aRange.aStart;
2359 break;
2360 case svSingleRef:
2361 PopSingleRef( aCellPos);
2362 break;
2363 default:
2364 PopError();
2365 SetError( FormulaError::NoRef);
2368 OUString aInfoType = GetString().getString();
2369 if (nGlobalError != FormulaError::NONE)
2370 PushIllegalParameter();
2371 else
2373 ScRefCellValue aCell(mrDoc, aCellPos);
2375 ScCellKeywordTranslator::transKeyword(aInfoType, &ScGlobal::GetLocale(), ocCell);
2377 // *** ADDRESS INFO ***
2378 if( aInfoType == "COL" )
2379 { // column number (1-based)
2380 PushInt( aCellPos.Col() + 1 );
2382 else if( aInfoType == "ROW" )
2383 { // row number (1-based)
2384 PushInt( aCellPos.Row() + 1 );
2386 else if( aInfoType == "SHEET" )
2387 { // table number (1-based)
2388 PushInt( aCellPos.Tab() + 1 );
2390 else if( aInfoType == "ADDRESS" )
2391 { // address formatted as [['FILENAME'#]$TABLE.]$COL$ROW
2393 // Follow the configurable string reference address syntax as also
2394 // used by INDIRECT() (and ADDRESS() for the sheet separator).
2395 FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax;
2396 switch (eConv)
2398 default:
2399 // Use the current address syntax if unspecified or says
2400 // one or the other or one we don't explicitly handle.
2401 eConv = mrDoc.GetAddressConvention();
2402 break;
2403 case FormulaGrammar::CONV_OOO:
2404 case FormulaGrammar::CONV_XL_A1:
2405 case FormulaGrammar::CONV_XL_R1C1:
2406 // Use that.
2407 break;
2410 ScRefFlags nFlags = (aCellPos.Tab() == aPos.Tab()) ? ScRefFlags::ADDR_ABS : ScRefFlags::ADDR_ABS_3D;
2411 OUString aStr(aCellPos.Format(nFlags, &mrDoc, eConv));
2412 PushString(aStr);
2414 else if( aInfoType == "FILENAME" )
2416 SCTAB nTab = aCellPos.Tab();
2417 OUString aFuncResult;
2418 if( nTab < mrDoc.GetTableCount() )
2420 if( mrDoc.GetLinkMode( nTab ) == ScLinkMode::VALUE )
2421 mrDoc.GetName( nTab, aFuncResult );
2422 else
2424 ScDocShell* pShell = mrDoc.GetDocumentShell();
2425 if( pShell && pShell->GetMedium() )
2427 const INetURLObject& rURLObj = pShell->GetMedium()->GetURLObject();
2428 OUString aTabName;
2429 mrDoc.GetName( nTab, aTabName );
2431 FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax;
2432 if (eConv == FormulaGrammar::CONV_UNSPECIFIED)
2433 eConv = mrDoc.GetAddressConvention();
2435 if (eConv == FormulaGrammar::CONV_XL_A1 ||
2436 eConv == FormulaGrammar::CONV_XL_R1C1 ||
2437 eConv == FormulaGrammar::CONV_XL_OOX)
2439 // file name and table name: FILEPATH/[FILENAME]TABLE
2440 if (!comphelper::LibreOfficeKit::isActive())
2441 aFuncResult = rURLObj.GetPartBeforeLastName();
2442 aFuncResult += "[" + rURLObj.GetLastName(INetURLObject::DecodeMechanism::Unambiguous) +
2443 "]" + aTabName;
2445 else
2447 // file name and table name: 'FILEPATH/FILENAME'#$TABLE
2448 aFuncResult = "'";
2449 if (!comphelper::LibreOfficeKit::isActive())
2450 aFuncResult += rURLObj.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous);
2451 else
2452 aFuncResult += rURLObj.GetLastName(INetURLObject::DecodeMechanism::Unambiguous);
2453 aFuncResult += "'#$" + aTabName;
2458 PushString( aFuncResult );
2460 else if( aInfoType == "COORD" )
2461 { // address, lotus 1-2-3 formatted: $TABLE:$COL$ROW
2462 // Yes, passing tab as col is intentional!
2463 OUString aCellStr1 =
2464 ScAddress( static_cast<SCCOL>(aCellPos.Tab()), 0, 0 ).Format(
2465 (ScRefFlags::COL_ABS|ScRefFlags::COL_VALID), nullptr, mrDoc.GetAddressConvention() );
2466 OUString aCellStr2 =
2467 aCellPos.Format((ScRefFlags::COL_ABS|ScRefFlags::COL_VALID|ScRefFlags::ROW_ABS|ScRefFlags::ROW_VALID),
2468 nullptr, mrDoc.GetAddressConvention());
2469 OUString aFuncResult = aCellStr1 + ":" + aCellStr2;
2470 PushString( aFuncResult );
2473 // *** CELL PROPERTIES ***
2474 else if( aInfoType == "CONTENTS" )
2475 { // contents of the cell, no formatting
2476 if (aCell.hasString())
2478 svl::SharedString aStr;
2479 GetCellString(aStr, aCell);
2480 PushString( aStr );
2482 else
2483 PushDouble(GetCellValue(aCellPos, aCell));
2485 else if( aInfoType == "TYPE" )
2486 { // b = blank; l = string (label); v = otherwise (value)
2487 sal_Unicode c;
2488 if (aCell.hasString())
2489 c = 'l';
2490 else
2491 c = aCell.hasNumeric() ? 'v' : 'b';
2492 PushString( OUString(c) );
2494 else if( aInfoType == "WIDTH" )
2495 { // column width (rounded off as count of zero characters in standard font and size)
2496 Printer* pPrinter = mrDoc.GetPrinter();
2497 MapMode aOldMode( pPrinter->GetMapMode() );
2498 vcl::Font aOldFont( pPrinter->GetFont() );
2499 vcl::Font aDefFont;
2501 pPrinter->SetMapMode(MapMode(MapUnit::MapTwip));
2502 // font color doesn't matter here
2503 mrDoc.getCellAttributeHelper().getDefaultCellAttribute().fillFontOnly(aDefFont, pPrinter);
2504 pPrinter->SetFont(aDefFont);
2505 tools::Long nZeroWidth = pPrinter->GetTextWidth( OUString( '0' ) );
2506 assert(nZeroWidth != 0);
2507 pPrinter->SetFont( aOldFont );
2508 pPrinter->SetMapMode( aOldMode );
2509 int nZeroCount = static_cast<int>(mrDoc.GetColWidth( aCellPos.Col(), aCellPos.Tab() ) / nZeroWidth);
2510 PushInt( nZeroCount );
2512 else if( aInfoType == "PREFIX" )
2513 { // ' = left; " = right; ^ = centered
2514 sal_Unicode c = 0;
2515 if (aCell.hasString())
2517 const SvxHorJustifyItem* pJustAttr = mrDoc.GetAttr( aCellPos, ATTR_HOR_JUSTIFY );
2518 switch( pJustAttr->GetValue() )
2520 case SvxCellHorJustify::Standard:
2521 case SvxCellHorJustify::Left:
2522 case SvxCellHorJustify::Block: c = '\''; break;
2523 case SvxCellHorJustify::Center: c = '^'; break;
2524 case SvxCellHorJustify::Right: c = '"'; break;
2525 case SvxCellHorJustify::Repeat: c = '\\'; break;
2528 PushString( OUString(c) );
2530 else if( aInfoType == "PROTECT" )
2531 { // 1 = cell locked
2532 const ScProtectionAttr* pProtAttr = mrDoc.GetAttr( aCellPos, ATTR_PROTECTION );
2533 PushInt( pProtAttr->GetProtection() ? 1 : 0 );
2536 // *** FORMATTING ***
2537 else if( aInfoType == "FORMAT" )
2538 { // specific format code for standard formats
2539 OUString aFuncResult;
2540 sal_uInt32 nFormat = mrDoc.GetNumberFormat( ScRange(aCellPos) );
2541 getFormatString(mrContext, nFormat, aFuncResult);
2542 PushString( aFuncResult );
2544 else if( aInfoType == "COLOR" )
2545 { // 1 = negative values are colored, otherwise 0
2546 const SvNumberformat* pFormat = mrContext.NFGetFormatEntry( mrDoc.GetNumberFormat( ScRange(aCellPos) ) );
2547 PushInt( lcl_FormatHasNegColor( pFormat ) ? 1 : 0 );
2549 else if( aInfoType == "PARENTHESES" )
2550 { // 1 = format string contains a '(' character, otherwise 0
2551 const SvNumberformat* pFormat = mrContext.NFGetFormatEntry( mrDoc.GetNumberFormat( ScRange(aCellPos) ) );
2552 PushInt( lcl_FormatHasOpenPar( pFormat ) ? 1 : 0 );
2554 else
2555 PushIllegalArgument();
2559 void ScInterpreter::ScCellExternal()
2561 sal_uInt16 nFileId;
2562 OUString aTabName;
2563 ScSingleRefData aRef;
2564 ScExternalRefCache::TokenRef pToken;
2565 ScExternalRefCache::CellFormat aFmt;
2566 PopExternalSingleRef(nFileId, aTabName, aRef, pToken, &aFmt);
2567 if (nGlobalError != FormulaError::NONE)
2569 PushError( nGlobalError);
2570 return;
2573 OUString aInfoType = GetString().getString();
2574 if (nGlobalError != FormulaError::NONE)
2576 PushError( nGlobalError);
2577 return;
2580 SCCOL nCol;
2581 SCROW nRow;
2582 SCTAB nTab;
2583 aRef.SetAbsTab(0); // external ref has a tab index of -1, which SingleRefToVars() don't like.
2584 SingleRefToVars(aRef, nCol, nRow, nTab);
2585 if (nGlobalError != FormulaError::NONE)
2587 PushIllegalParameter();
2588 return;
2590 aRef.SetAbsTab(-1); // revert the value.
2592 ScCellKeywordTranslator::transKeyword(aInfoType, &ScGlobal::GetLocale(), ocCell);
2593 ScExternalRefManager* pRefMgr = mrDoc.GetExternalRefManager();
2595 if ( aInfoType == "COL" )
2596 PushInt(nCol + 1);
2597 else if ( aInfoType == "ROW" )
2598 PushInt(nRow + 1);
2599 else if ( aInfoType == "SHEET" )
2601 // For SHEET, No idea what number we should set, but let's always set
2602 // 1 if the external sheet exists, no matter what sheet. Excel does
2603 // the same.
2604 if (pRefMgr->getCacheTable(nFileId, aTabName, false))
2605 PushInt(1);
2606 else
2607 SetError(FormulaError::NoName);
2609 else if ( aInfoType == "ADDRESS" )
2611 // ODF 1.2 says we need to always display address using the ODF A1 grammar.
2612 ScTokenArray aArray(mrDoc);
2613 aArray.AddExternalSingleReference(nFileId, svl::SharedString( aTabName), aRef); // string not interned
2614 ScCompiler aComp(mrDoc, aPos, aArray, formula::FormulaGrammar::GRAM_ODFF_A1);
2615 OUString aStr;
2616 aComp.CreateStringFromTokenArray(aStr);
2617 PushString(aStr);
2619 else if ( aInfoType == "FILENAME" )
2621 const OUString* p = pRefMgr->getExternalFileName(nFileId);
2622 if (!p)
2624 // In theory this should never happen...
2625 SetError(FormulaError::NoName);
2626 return;
2629 OUString aBuf;
2630 FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax;
2631 if (eConv == FormulaGrammar::CONV_UNSPECIFIED)
2632 eConv = mrDoc.GetAddressConvention();
2634 if (eConv == FormulaGrammar::CONV_XL_A1 ||
2635 eConv == FormulaGrammar::CONV_XL_R1C1 ||
2636 eConv == FormulaGrammar::CONV_XL_OOX)
2638 // 'file URI/[FileName]SheetName
2639 sal_Int32 nPos = p->lastIndexOf('/');
2640 aBuf = OUString::Concat(p->subView(0, nPos + 1))
2641 + "[" + p->subView(nPos + 1) + "]"
2642 + aTabName;
2644 else
2646 // 'file URI'#$SheetName
2647 aBuf = "'" + *p + "'#$" + aTabName;
2650 PushString(aBuf);
2652 else if ( aInfoType == "CONTENTS" )
2654 switch (pToken->GetType())
2656 case svString:
2657 PushString(pToken->GetString());
2658 break;
2659 case svDouble:
2660 PushString(OUString::number(pToken->GetDouble()));
2661 break;
2662 case svError:
2663 PushString(ScGlobal::GetErrorString(pToken->GetError()));
2664 break;
2665 default:
2666 PushString(OUString());
2669 else if ( aInfoType == "TYPE" )
2671 sal_Unicode c = 'v';
2672 switch (pToken->GetType())
2674 case svString:
2675 c = 'l';
2676 break;
2677 case svEmptyCell:
2678 c = 'b';
2679 break;
2680 default:
2683 PushString(OUString(c));
2685 else if ( aInfoType == "FORMAT" )
2687 OUString aFmtStr;
2688 sal_uLong nFmt = aFmt.mbIsSet ? aFmt.mnIndex : 0;
2689 getFormatString(mrContext, nFmt, aFmtStr);
2690 PushString(aFmtStr);
2692 else if ( aInfoType == "COLOR" )
2694 // 1 = negative values are colored, otherwise 0
2695 int nVal = 0;
2696 if (aFmt.mbIsSet)
2698 const SvNumberformat* pFormat = mrContext.NFGetFormatEntry(aFmt.mnIndex);
2699 nVal = lcl_FormatHasNegColor(pFormat) ? 1 : 0;
2701 PushInt(nVal);
2703 else if ( aInfoType == "PARENTHESES" )
2705 // 1 = format string contains a '(' character, otherwise 0
2706 int nVal = 0;
2707 if (aFmt.mbIsSet)
2709 const SvNumberformat* pFormat = mrContext.NFGetFormatEntry(aFmt.mnIndex);
2710 nVal = lcl_FormatHasOpenPar(pFormat) ? 1 : 0;
2712 PushInt(nVal);
2714 else
2715 PushIllegalParameter();
2718 void ScInterpreter::ScIsRef()
2720 nFuncFmtType = SvNumFormatType::LOGICAL;
2721 bool bRes = false;
2722 switch ( GetStackType() )
2724 case svSingleRef :
2726 ScAddress aAdr;
2727 PopSingleRef( aAdr );
2728 if ( nGlobalError == FormulaError::NONE )
2729 bRes = true;
2731 break;
2732 case svDoubleRef :
2734 ScRange aRange;
2735 PopDoubleRef( aRange );
2736 if ( nGlobalError == FormulaError::NONE )
2737 bRes = true;
2739 break;
2740 case svRefList :
2742 FormulaConstTokenRef x = PopToken();
2743 if ( nGlobalError == FormulaError::NONE )
2744 bRes = !x->GetRefList()->empty();
2746 break;
2747 case svExternalSingleRef:
2749 ScExternalRefCache::TokenRef pToken;
2750 PopExternalSingleRef(pToken);
2751 if (nGlobalError == FormulaError::NONE)
2752 bRes = true;
2754 break;
2755 case svExternalDoubleRef:
2757 ScExternalRefCache::TokenArrayRef pArray;
2758 PopExternalDoubleRef(pArray);
2759 if (nGlobalError == FormulaError::NONE)
2760 bRes = true;
2762 break;
2763 default:
2764 Pop();
2766 nGlobalError = FormulaError::NONE;
2767 PushInt( int(bRes) );
2770 void ScInterpreter::ScIsValue()
2772 nFuncFmtType = SvNumFormatType::LOGICAL;
2773 bool bRes = false;
2774 switch ( GetRawStackType() )
2776 case svDouble:
2777 Pop();
2778 bRes = true;
2779 break;
2780 case svDoubleRef :
2781 case svSingleRef :
2783 ScAddress aAdr;
2784 if ( !PopDoubleRefOrSingleRef( aAdr ) )
2785 break;
2787 ScRefCellValue aCell(mrDoc, aAdr);
2788 if (GetCellErrCode(aCell) == FormulaError::NONE)
2790 switch (aCell.getType())
2792 case CELLTYPE_VALUE :
2793 bRes = true;
2794 break;
2795 case CELLTYPE_FORMULA :
2796 bRes = (aCell.getFormula()->IsValue() && !aCell.getFormula()->IsEmpty());
2797 break;
2798 default:
2799 ; // nothing
2803 break;
2804 case svExternalSingleRef:
2806 ScExternalRefCache::TokenRef pToken;
2807 PopExternalSingleRef(pToken);
2808 if (nGlobalError == FormulaError::NONE && pToken->GetType() == svDouble)
2809 bRes = true;
2811 break;
2812 case svExternalDoubleRef:
2813 case svMatrix:
2815 ScMatrixRef pMat = GetMatrix();
2816 if ( !pMat )
2817 ; // nothing
2818 else if ( !pJumpMatrix )
2820 if (pMat->GetErrorIfNotString( 0, 0) == FormulaError::NONE)
2821 bRes = pMat->IsValue( 0, 0);
2823 else
2825 SCSIZE nCols, nRows, nC, nR;
2826 pMat->GetDimensions( nCols, nRows);
2827 pJumpMatrix->GetPos( nC, nR);
2828 if ( nC < nCols && nR < nRows )
2829 if (pMat->GetErrorIfNotString( nC, nR) == FormulaError::NONE)
2830 bRes = pMat->IsValue( nC, nR);
2833 break;
2834 default:
2835 Pop();
2837 nGlobalError = FormulaError::NONE;
2838 PushInt( int(bRes) );
2841 void ScInterpreter::ScIsFormula()
2843 nFuncFmtType = SvNumFormatType::LOGICAL;
2844 bool bRes = false;
2845 switch ( GetStackType() )
2847 case svDoubleRef :
2848 if (IsInArrayContext())
2850 SCCOL nCol1, nCol2;
2851 SCROW nRow1, nRow2;
2852 SCTAB nTab1, nTab2;
2853 PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
2854 if (nGlobalError != FormulaError::NONE)
2856 PushError( nGlobalError);
2857 return;
2859 if (nTab1 != nTab2)
2861 PushIllegalArgument();
2862 return;
2865 ScMatrixRef pResMat = GetNewMat( static_cast<SCSIZE>(nCol2 - nCol1 + 1),
2866 static_cast<SCSIZE>(nRow2 - nRow1 + 1), true);
2867 if (!pResMat)
2869 PushError( FormulaError::MatrixSize);
2870 return;
2873 /* TODO: we really should have a gap-aware cell iterator. */
2874 SCSIZE i=0, j=0;
2875 ScAddress aAdr( 0, 0, nTab1);
2876 for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
2878 aAdr.SetCol(nCol);
2879 for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
2881 aAdr.SetRow(nRow);
2882 ScRefCellValue aCell(mrDoc, aAdr);
2883 pResMat->PutBoolean( (aCell.getType() == CELLTYPE_FORMULA), i,j);
2884 ++j;
2886 ++i;
2887 j = 0;
2890 PushMatrix( pResMat);
2891 return;
2893 [[fallthrough]];
2894 case svSingleRef :
2896 ScAddress aAdr;
2897 if ( !PopDoubleRefOrSingleRef( aAdr ) )
2898 break;
2900 bRes = (mrDoc.GetCellType(aAdr) == CELLTYPE_FORMULA);
2902 break;
2903 default:
2904 Pop();
2906 nGlobalError = FormulaError::NONE;
2907 PushInt( int(bRes) );
2910 void ScInterpreter::ScFormula()
2912 OUString aFormula;
2913 switch ( GetStackType() )
2915 case svDoubleRef :
2916 if (IsInArrayContext())
2918 SCCOL nCol1, nCol2;
2919 SCROW nRow1, nRow2;
2920 SCTAB nTab1, nTab2;
2921 PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
2922 if (nGlobalError != FormulaError::NONE)
2923 break;
2925 if (nTab1 != nTab2)
2927 SetError( FormulaError::IllegalArgument);
2928 break;
2931 ScMatrixRef pResMat = GetNewMat( nCol2 - nCol1 + 1, nRow2 - nRow1 + 1, true);
2932 if (!pResMat)
2933 break;
2935 /* TODO: use a column iterator instead? */
2936 SCSIZE i=0, j=0;
2937 ScAddress aAdr(0,0,nTab1);
2938 for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
2940 aAdr.SetCol(nCol);
2941 for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
2943 aAdr.SetRow(nRow);
2944 ScRefCellValue aCell(mrDoc, aAdr);
2945 switch (aCell.getType())
2947 case CELLTYPE_FORMULA :
2948 aFormula = aCell.getFormula()->GetFormula(formula::FormulaGrammar::GRAM_UNSPECIFIED, &mrContext);
2949 pResMat->PutString( mrStrPool.intern( aFormula), i,j);
2950 break;
2951 default:
2952 pResMat->PutError( FormulaError::NotAvailable, i,j);
2954 ++j;
2956 ++i;
2957 j = 0;
2960 PushMatrix( pResMat);
2961 return;
2963 [[fallthrough]];
2964 case svSingleRef :
2966 ScAddress aAdr;
2967 if ( !PopDoubleRefOrSingleRef( aAdr ) )
2968 break;
2970 ScRefCellValue aCell(mrDoc, aAdr);
2971 switch (aCell.getType())
2973 case CELLTYPE_FORMULA :
2974 aFormula = aCell.getFormula()->GetFormula(formula::FormulaGrammar::GRAM_UNSPECIFIED, &mrContext);
2975 break;
2976 default:
2977 SetError( FormulaError::NotAvailable );
2980 break;
2981 default:
2982 PopError();
2983 SetError( FormulaError::NotAvailable );
2985 PushString( aFormula );
2988 void ScInterpreter::ScIsNV()
2990 nFuncFmtType = SvNumFormatType::LOGICAL;
2991 bool bRes = false;
2992 switch ( GetStackType() )
2994 case svDoubleRef :
2995 case svSingleRef :
2997 ScAddress aAdr;
2998 bool bOk = PopDoubleRefOrSingleRef( aAdr );
2999 if ( nGlobalError == FormulaError::NotAvailable )
3000 bRes = true;
3001 else if (bOk)
3003 ScRefCellValue aCell(mrDoc, aAdr);
3004 FormulaError nErr = GetCellErrCode(aCell);
3005 bRes = (nErr == FormulaError::NotAvailable);
3008 break;
3009 case svExternalSingleRef:
3011 ScExternalRefCache::TokenRef pToken;
3012 PopExternalSingleRef(pToken);
3013 if (nGlobalError == FormulaError::NotAvailable ||
3014 (pToken && pToken->GetType() == svError && pToken->GetError() == FormulaError::NotAvailable))
3015 bRes = true;
3017 break;
3018 case svExternalDoubleRef:
3019 case svMatrix:
3021 ScMatrixRef pMat = GetMatrix();
3022 if ( !pMat )
3023 ; // nothing
3024 else if ( !pJumpMatrix )
3025 bRes = (pMat->GetErrorIfNotString( 0, 0) == FormulaError::NotAvailable);
3026 else
3028 SCSIZE nCols, nRows, nC, nR;
3029 pMat->GetDimensions( nCols, nRows);
3030 pJumpMatrix->GetPos( nC, nR);
3031 if ( nC < nCols && nR < nRows )
3032 bRes = (pMat->GetErrorIfNotString( nC, nR) == FormulaError::NotAvailable);
3035 break;
3036 default:
3037 PopError();
3038 if ( nGlobalError == FormulaError::NotAvailable )
3039 bRes = true;
3041 nGlobalError = FormulaError::NONE;
3042 PushInt( int(bRes) );
3045 void ScInterpreter::ScIsErr()
3047 nFuncFmtType = SvNumFormatType::LOGICAL;
3048 bool bRes = false;
3049 switch ( GetStackType() )
3051 case svDoubleRef :
3052 case svSingleRef :
3054 ScAddress aAdr;
3055 bool bOk = PopDoubleRefOrSingleRef( aAdr );
3056 if ( !bOk || (nGlobalError != FormulaError::NONE && nGlobalError != FormulaError::NotAvailable) )
3057 bRes = true;
3058 else
3060 ScRefCellValue aCell(mrDoc, aAdr);
3061 FormulaError nErr = GetCellErrCode(aCell);
3062 bRes = (nErr != FormulaError::NONE && nErr != FormulaError::NotAvailable);
3065 break;
3066 case svExternalSingleRef:
3068 ScExternalRefCache::TokenRef pToken;
3069 PopExternalSingleRef(pToken);
3070 if ((nGlobalError != FormulaError::NONE && nGlobalError != FormulaError::NotAvailable) || !pToken ||
3071 (pToken->GetType() == svError && pToken->GetError() != FormulaError::NotAvailable))
3072 bRes = true;
3074 break;
3075 case svExternalDoubleRef:
3076 case svMatrix:
3078 ScMatrixRef pMat = GetMatrix();
3079 if ( nGlobalError != FormulaError::NONE || !pMat )
3080 bRes = ((nGlobalError != FormulaError::NONE && nGlobalError != FormulaError::NotAvailable) || !pMat);
3081 else if ( !pJumpMatrix )
3083 FormulaError nErr = pMat->GetErrorIfNotString( 0, 0);
3084 bRes = (nErr != FormulaError::NONE && nErr != FormulaError::NotAvailable);
3086 else
3088 SCSIZE nCols, nRows, nC, nR;
3089 pMat->GetDimensions( nCols, nRows);
3090 pJumpMatrix->GetPos( nC, nR);
3091 if ( nC < nCols && nR < nRows )
3093 FormulaError nErr = pMat->GetErrorIfNotString( nC, nR);
3094 bRes = (nErr != FormulaError::NONE && nErr != FormulaError::NotAvailable);
3098 break;
3099 default:
3100 PopError();
3101 if ( nGlobalError != FormulaError::NONE && nGlobalError != FormulaError::NotAvailable )
3102 bRes = true;
3104 nGlobalError = FormulaError::NONE;
3105 PushInt( int(bRes) );
3108 void ScInterpreter::ScIsError()
3110 nFuncFmtType = SvNumFormatType::LOGICAL;
3111 bool bRes = false;
3112 switch ( GetStackType() )
3114 case svDoubleRef :
3115 case svSingleRef :
3117 ScAddress aAdr;
3118 if ( !PopDoubleRefOrSingleRef( aAdr ) )
3120 bRes = true;
3121 break;
3123 if ( nGlobalError != FormulaError::NONE )
3124 bRes = true;
3125 else
3127 ScRefCellValue aCell(mrDoc, aAdr);
3128 bRes = (GetCellErrCode(aCell) != FormulaError::NONE);
3131 break;
3132 case svExternalSingleRef:
3134 ScExternalRefCache::TokenRef pToken;
3135 PopExternalSingleRef(pToken);
3136 if (nGlobalError != FormulaError::NONE || pToken->GetType() == svError)
3137 bRes = true;
3139 break;
3140 case svExternalDoubleRef:
3141 case svMatrix:
3143 ScMatrixRef pMat = GetMatrix();
3144 if ( nGlobalError != FormulaError::NONE || !pMat )
3145 bRes = true;
3146 else if ( !pJumpMatrix )
3147 bRes = (pMat->GetErrorIfNotString( 0, 0) != FormulaError::NONE);
3148 else
3150 SCSIZE nCols, nRows, nC, nR;
3151 pMat->GetDimensions( nCols, nRows);
3152 pJumpMatrix->GetPos( nC, nR);
3153 if ( nC < nCols && nR < nRows )
3154 bRes = (pMat->GetErrorIfNotString( nC, nR) != FormulaError::NONE);
3157 break;
3158 default:
3159 PopError();
3160 if ( nGlobalError != FormulaError::NONE )
3161 bRes = true;
3163 nGlobalError = FormulaError::NONE;
3164 PushInt( int(bRes) );
3167 bool ScInterpreter::IsEven()
3169 nFuncFmtType = SvNumFormatType::LOGICAL;
3170 bool bRes = false;
3171 double fVal = 0.0;
3172 switch ( GetStackType() )
3174 case svDoubleRef :
3175 case svSingleRef :
3177 ScAddress aAdr;
3178 if ( !PopDoubleRefOrSingleRef( aAdr ) )
3179 break;
3181 ScRefCellValue aCell(mrDoc, aAdr);
3182 FormulaError nErr = GetCellErrCode(aCell);
3183 if (nErr != FormulaError::NONE)
3184 SetError(nErr);
3185 else
3187 switch (aCell.getType())
3189 case CELLTYPE_VALUE :
3190 fVal = GetCellValue(aAdr, aCell);
3191 bRes = true;
3192 break;
3193 case CELLTYPE_FORMULA :
3194 if (aCell.getFormula()->IsValue())
3196 fVal = GetCellValue(aAdr, aCell);
3197 bRes = true;
3199 break;
3200 default:
3201 ; // nothing
3205 break;
3206 case svDouble:
3208 fVal = PopDouble();
3209 bRes = true;
3211 break;
3212 case svExternalSingleRef:
3214 ScExternalRefCache::TokenRef pToken;
3215 PopExternalSingleRef(pToken);
3216 if (nGlobalError == FormulaError::NONE && pToken->GetType() == svDouble)
3218 fVal = pToken->GetDouble();
3219 bRes = true;
3222 break;
3223 case svExternalDoubleRef:
3224 case svMatrix:
3226 ScMatrixRef pMat = GetMatrix();
3227 if ( !pMat )
3228 ; // nothing
3229 else if ( !pJumpMatrix )
3231 bRes = pMat->IsValue( 0, 0);
3232 if ( bRes )
3233 fVal = pMat->GetDouble( 0, 0);
3235 else
3237 SCSIZE nCols, nRows, nC, nR;
3238 pMat->GetDimensions( nCols, nRows);
3239 pJumpMatrix->GetPos( nC, nR);
3240 if ( nC < nCols && nR < nRows )
3242 bRes = pMat->IsValue( nC, nR);
3243 if ( bRes )
3244 fVal = pMat->GetDouble( nC, nR);
3246 else
3247 SetError( FormulaError::NoValue);
3250 break;
3251 default:
3252 ; // nothing
3254 if ( !bRes )
3255 SetError( FormulaError::IllegalParameter);
3256 else
3257 bRes = ( fmod( ::rtl::math::approxFloor( fabs( fVal ) ), 2.0 ) < 0.5 );
3258 return bRes;
3261 void ScInterpreter::ScIsEven()
3263 PushInt( int(IsEven()) );
3266 void ScInterpreter::ScIsOdd()
3268 PushInt( int(!IsEven()) );
3271 void ScInterpreter::ScN()
3273 FormulaError nErr = nGlobalError;
3274 nGlobalError = FormulaError::NONE;
3275 // Temporarily override the ConvertStringToValue() error for
3276 // GetCellValue() / GetCellValueOrZero()
3277 FormulaError nSErr = mnStringNoValueError;
3278 mnStringNoValueError = FormulaError::CellNoValue;
3279 double fVal = GetDouble();
3280 mnStringNoValueError = nSErr;
3281 if (nErr != FormulaError::NONE)
3282 nGlobalError = nErr; // preserve previous error if any
3283 else if (nGlobalError == FormulaError::CellNoValue)
3284 nGlobalError = FormulaError::NONE; // reset temporary detection error
3285 PushDouble(fVal);
3288 void ScInterpreter::ScTrim()
3290 // Doesn't only trim but also removes duplicated blanks within!
3291 OUString aVal = comphelper::string::strip(GetString().getString(), ' ');
3292 OUStringBuffer aStr;
3293 const sal_Unicode* p = aVal.getStr();
3294 const sal_Unicode* const pEnd = p + aVal.getLength();
3295 while ( p < pEnd )
3297 if ( *p != ' ' || p[-1] != ' ' ) // first can't be ' ', so -1 is fine
3298 aStr.append(*p);
3299 p++;
3301 PushString(aStr.makeStringAndClear());
3304 void ScInterpreter::ScUpper()
3306 OUString aString = ScGlobal::getCharClass().uppercase(GetString().getString());
3307 PushString(aString);
3310 void ScInterpreter::ScProper()
3312 //2do: what to do with I18N-CJK ?!?
3313 OUStringBuffer aStr(GetString().getString());
3314 const sal_Int32 nLen = aStr.getLength();
3315 if ( nLen > 0 )
3317 OUString aUpr(ScGlobal::getCharClass().uppercase(aStr.toString()));
3318 OUString aLwr(ScGlobal::getCharClass().lowercase(aStr.toString()));
3319 aStr[0] = aUpr[0];
3320 sal_Int32 nPos = 1;
3321 while( nPos < nLen )
3323 OUString aTmpStr( aStr[nPos-1] );
3324 if ( !ScGlobal::getCharClass().isLetter( aTmpStr, 0 ) )
3325 aStr[nPos] = aUpr[nPos];
3326 else
3327 aStr[nPos] = aLwr[nPos];
3328 ++nPos;
3331 PushString(aStr.makeStringAndClear());
3334 void ScInterpreter::ScLower()
3336 OUString aString = ScGlobal::getCharClass().lowercase(GetString().getString());
3337 PushString(aString);
3340 void ScInterpreter::ScLen()
3342 OUString aStr = GetString().getString();
3343 sal_Int32 nIdx = 0;
3344 sal_Int32 nCnt = 0;
3345 while ( nIdx < aStr.getLength() )
3347 aStr.iterateCodePoints( &nIdx );
3348 ++nCnt;
3350 PushDouble( nCnt );
3353 void ScInterpreter::ScT()
3355 switch ( GetStackType() )
3357 case svDoubleRef :
3358 case svSingleRef :
3360 ScAddress aAdr;
3361 if ( !PopDoubleRefOrSingleRef( aAdr ) )
3363 PushInt(0);
3364 return ;
3366 bool bValue = false;
3367 ScRefCellValue aCell(mrDoc, aAdr);
3368 if (GetCellErrCode(aCell) == FormulaError::NONE)
3370 switch (aCell.getType())
3372 case CELLTYPE_VALUE :
3373 bValue = true;
3374 break;
3375 case CELLTYPE_FORMULA :
3376 bValue = aCell.getFormula()->IsValue();
3377 break;
3378 default:
3379 ; // nothing
3382 if ( bValue )
3383 PushString(OUString());
3384 else
3386 // like GetString()
3387 svl::SharedString aStr;
3388 GetCellString(aStr, aCell);
3389 PushString(aStr);
3392 break;
3393 case svMatrix:
3394 case svExternalSingleRef:
3395 case svExternalDoubleRef:
3397 double fVal;
3398 svl::SharedString aStr;
3399 ScMatValType nMatValType = GetDoubleOrStringFromMatrix( fVal, aStr);
3400 if (ScMatrix::IsValueType( nMatValType))
3401 PushString(svl::SharedString::getEmptyString());
3402 else
3403 PushString( aStr);
3405 break;
3406 case svDouble :
3408 PopError();
3409 PushString( OUString() );
3411 break;
3412 case svString :
3413 ; // leave on stack
3414 break;
3415 default :
3416 PushError( FormulaError::UnknownOpCode);
3420 void ScInterpreter::ScValue()
3422 OUString aInputString;
3423 double fVal;
3425 switch ( GetRawStackType() )
3427 case svMissing:
3428 case svEmptyCell:
3429 Pop();
3430 PushInt(0);
3431 return;
3432 case svDouble:
3433 return; // leave on stack
3435 case svSingleRef:
3436 case svDoubleRef:
3438 ScAddress aAdr;
3439 if ( !PopDoubleRefOrSingleRef( aAdr ) )
3441 PushInt(0);
3442 return;
3444 ScRefCellValue aCell(mrDoc, aAdr);
3445 if (aCell.hasString())
3447 svl::SharedString aSS;
3448 GetCellString(aSS, aCell);
3449 aInputString = aSS.getString();
3451 else if (aCell.hasNumeric())
3453 PushDouble( GetCellValue(aAdr, aCell) );
3454 return;
3456 else
3458 PushDouble(0.0);
3459 return;
3462 break;
3463 case svMatrix:
3465 svl::SharedString aSS;
3466 ScMatValType nType = GetDoubleOrStringFromMatrix( fVal,
3467 aSS);
3468 aInputString = aSS.getString();
3469 switch (nType)
3471 case ScMatValType::Empty:
3472 fVal = 0.0;
3473 [[fallthrough]];
3474 case ScMatValType::Value:
3475 case ScMatValType::Boolean:
3476 PushDouble( fVal);
3477 return;
3478 case ScMatValType::String:
3479 // evaluated below
3480 break;
3481 default:
3482 PushIllegalArgument();
3485 break;
3486 default:
3487 aInputString = GetString().getString();
3488 break;
3491 sal_uInt32 nFIndex = 0; // 0 for default locale
3492 if (mrContext.NFIsNumberFormat(aInputString, nFIndex, fVal))
3493 PushDouble(fVal);
3494 else
3495 PushIllegalArgument();
3498 // fdo#57180
3499 void ScInterpreter::ScNumberValue()
3502 sal_uInt8 nParamCount = GetByte();
3503 if ( !MustHaveParamCount( nParamCount, 1, 3 ) )
3504 return;
3506 OUString aInputString;
3507 OUString aGroupSeparator;
3508 sal_Unicode cDecimalSeparator = 0;
3510 if ( nParamCount == 3 )
3511 aGroupSeparator = GetString().getString();
3513 if ( nParamCount >= 2 )
3515 OUString aDecimalSeparator = GetString().getString();
3516 if ( aDecimalSeparator.getLength() == 1 )
3517 cDecimalSeparator = aDecimalSeparator[ 0 ];
3518 else
3520 PushIllegalArgument(); //if given, separator length must be 1
3521 return;
3525 if ( cDecimalSeparator && aGroupSeparator.indexOf( cDecimalSeparator ) != -1 )
3527 PushIllegalArgument(); //decimal separator cannot appear in group separator
3528 return;
3531 switch (GetStackType())
3533 case svDouble:
3534 return; // leave on stack
3535 default:
3536 aInputString = GetString().getString();
3538 if ( nGlobalError != FormulaError::NONE )
3540 PushError( nGlobalError );
3541 return;
3543 if ( aInputString.isEmpty() )
3545 if ( maCalcConfig.mbEmptyStringAsZero )
3546 PushDouble( 0.0 );
3547 else
3548 PushNoValue();
3549 return;
3552 sal_Int32 nDecSep = aInputString.indexOf( cDecimalSeparator );
3553 if ( nDecSep != 0 )
3555 OUString aTemporary( nDecSep >= 0 ? aInputString.copy( 0, nDecSep ) : aInputString );
3556 sal_Int32 nIndex = 0;
3557 while (nIndex < aGroupSeparator.getLength())
3559 sal_uInt32 nChar = aGroupSeparator.iterateCodePoints( &nIndex );
3560 aTemporary = aTemporary.replaceAll( OUString( &nChar, 1 ), "" );
3562 if ( nDecSep >= 0 )
3563 aInputString = aTemporary + aInputString.subView( nDecSep );
3564 else
3565 aInputString = aTemporary;
3568 for ( sal_Int32 i = aInputString.getLength(); --i >= 0; )
3570 sal_Unicode c = aInputString[ i ];
3571 if ( c == 0x0020 || c == 0x0009 || c == 0x000A || c == 0x000D )
3572 aInputString = aInputString.replaceAt( i, 1, u"" ); // remove spaces etc.
3574 sal_Int32 nPercentCount = 0;
3575 for ( sal_Int32 i = aInputString.getLength() - 1; i >= 0 && aInputString[ i ] == 0x0025; i-- )
3577 aInputString = aInputString.replaceAt( i, 1, u"" ); // remove and count trailing '%'
3578 nPercentCount++;
3581 rtl_math_ConversionStatus eStatus;
3582 sal_Int32 nParseEnd;
3583 double fVal = ::rtl::math::stringToDouble( aInputString, cDecimalSeparator, 0, &eStatus, &nParseEnd );
3584 if ( eStatus == rtl_math_ConversionStatus_Ok && nParseEnd == aInputString.getLength() )
3586 if (nPercentCount)
3587 fVal *= pow( 10.0, -(nPercentCount * 2)); // process '%' from input string
3588 PushDouble(fVal);
3589 return;
3591 PushNoValue();
3594 static bool lcl_ScInterpreter_IsPrintable( sal_uInt32 nCodePoint )
3596 return ( !u_isISOControl(nCodePoint) /*not in Cc*/
3597 && u_isdefined(nCodePoint) /*not in Cn*/ );
3601 void ScInterpreter::ScClean()
3603 OUString aStr = GetString().getString();
3605 OUStringBuffer aBuf( aStr.getLength() );
3606 sal_Int32 nIdx = 0;
3607 while ( nIdx < aStr.getLength() )
3609 sal_uInt32 c = aStr.iterateCodePoints( &nIdx );
3610 if ( lcl_ScInterpreter_IsPrintable( c ) )
3611 aBuf.appendUtf32( c );
3613 PushString( aBuf.makeStringAndClear() );
3617 void ScInterpreter::ScCode()
3619 //2do: make it full range unicode?
3620 OUString aStr = GetString().getString();
3621 if (aStr.isEmpty())
3622 PushInt(0);
3623 else
3625 //"classic" ByteString conversion flags
3626 const sal_uInt32 convertFlags =
3627 RTL_UNICODETOTEXT_FLAGS_NONSPACING_IGNORE |
3628 RTL_UNICODETOTEXT_FLAGS_CONTROL_IGNORE |
3629 RTL_UNICODETOTEXT_FLAGS_FLUSH |
3630 RTL_UNICODETOTEXT_FLAGS_UNDEFINED_DEFAULT |
3631 RTL_UNICODETOTEXT_FLAGS_INVALID_DEFAULT |
3632 RTL_UNICODETOTEXT_FLAGS_UNDEFINED_REPLACE;
3633 PushInt( static_cast<unsigned char>(OUStringToOString(OUStringChar(aStr[0]), osl_getThreadTextEncoding(), convertFlags).toChar()) );
3637 void ScInterpreter::ScChar()
3639 //2do: make it full range unicode?
3640 double fVal = GetDouble();
3641 if (fVal < 0.0 || fVal >= 256.0)
3642 PushIllegalArgument();
3643 else
3645 //"classic" ByteString conversion flags
3646 const sal_uInt32 convertFlags =
3647 RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_DEFAULT |
3648 RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_DEFAULT |
3649 RTL_TEXTTOUNICODE_FLAGS_INVALID_DEFAULT;
3651 char cEncodedChar = static_cast<char>(fVal);
3652 OUString aStr(&cEncodedChar, 1, osl_getThreadTextEncoding(), convertFlags);
3653 PushString(aStr);
3657 /* #i70213# fullwidth/halfwidth conversion provided by
3658 * Takashi Nakamoto <bluedwarf@ooo>
3659 * erAck: added Excel compatibility conversions as seen in issue's test case. */
3661 static OUString lcl_convertIntoHalfWidth( const OUString & rStr )
3663 // Make the initialization thread-safe. Since another function needs to be called, move it all to another
3664 // function and thread-safely initialize a static reference in this function.
3665 auto init = []() -> utl::TransliterationWrapper&
3667 static utl::TransliterationWrapper trans( ::comphelper::getProcessComponentContext(), TransliterationFlags::NONE );
3668 trans.loadModuleByImplName( u"FULLWIDTH_HALFWIDTH_LIKE_ASC"_ustr, LANGUAGE_SYSTEM );
3669 return trans;
3671 static utl::TransliterationWrapper& aTrans( init());
3672 return aTrans.transliterate( rStr, 0, sal_uInt16( rStr.getLength() ) );
3675 static OUString lcl_convertIntoFullWidth( const OUString & rStr )
3677 auto init = []() -> utl::TransliterationWrapper&
3679 static utl::TransliterationWrapper trans( ::comphelper::getProcessComponentContext(), TransliterationFlags::NONE );
3680 trans.loadModuleByImplName( u"HALFWIDTH_FULLWIDTH_LIKE_JIS"_ustr, LANGUAGE_SYSTEM );
3681 return trans;
3683 static utl::TransliterationWrapper& aTrans( init());
3684 return aTrans.transliterate( rStr, 0, sal_uInt16( rStr.getLength() ) );
3687 /* ODFF:
3688 * Summary: Converts half-width to full-width ASCII and katakana characters.
3689 * Semantics: Conversion is done for half-width ASCII and katakana characters,
3690 * other characters are simply copied from T to the result. This is the
3691 * complementary function to ASC.
3692 * For references regarding halfwidth and fullwidth characters see
3693 * http://www.unicode.org/reports/tr11/
3694 * http://www.unicode.org/charts/charindex2.html#H
3695 * http://www.unicode.org/charts/charindex2.html#F
3697 void ScInterpreter::ScJis()
3699 if (MustHaveParamCount( GetByte(), 1))
3700 PushString( lcl_convertIntoFullWidth( GetString().getString()));
3703 /* ODFF:
3704 * Summary: Converts full-width to half-width ASCII and katakana characters.
3705 * Semantics: Conversion is done for full-width ASCII and katakana characters,
3706 * other characters are simply copied from T to the result. This is the
3707 * complementary function to JIS.
3709 void ScInterpreter::ScAsc()
3711 if (MustHaveParamCount( GetByte(), 1))
3712 PushString( lcl_convertIntoHalfWidth( GetString().getString()));
3715 void ScInterpreter::ScUnicode()
3717 if ( MustHaveParamCount( GetByte(), 1 ) )
3719 OUString aStr = GetString().getString();
3720 if (aStr.isEmpty())
3721 PushIllegalParameter();
3722 else
3724 PushDouble(aStr.iterateCodePoints(&o3tl::temporary(sal_Int32(0))));
3729 void ScInterpreter::ScUnichar()
3731 if ( MustHaveParamCount( GetByte(), 1 ) )
3733 sal_uInt32 nCodePoint = GetUInt32();
3734 if (nGlobalError != FormulaError::NONE || !rtl::isUnicodeCodePoint(nCodePoint))
3735 PushIllegalArgument();
3736 else
3738 OUString aStr( &nCodePoint, 1 );
3739 PushString( aStr );
3744 bool ScInterpreter::SwitchToArrayRefList( ScMatrixRef& xResMat, SCSIZE nMatRows, double fCurrent,
3745 const std::function<void( SCSIZE i, double fCurrent )>& MatOpFunc, bool bDoMatOp )
3747 const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]);
3748 if (!p || !p->IsArrayResult())
3749 return false;
3751 if (!xResMat)
3753 // Create and init all elements with current value.
3754 assert(nMatRows > 0);
3755 xResMat = GetNewMat( 1, nMatRows, true);
3756 xResMat->FillDouble( fCurrent, 0,0, 0,nMatRows-1);
3758 else if (bDoMatOp)
3760 // Current value and values from vector are operands
3761 // for each vector position.
3762 for (SCSIZE i=0; i < nMatRows; ++i)
3764 MatOpFunc( i, fCurrent);
3767 return true;
3770 void ScInterpreter::ScMin( bool bTextAsZero )
3772 short nParamCount = GetByte();
3773 if (!MustHaveParamCountMin( nParamCount, 1))
3774 return;
3776 ScMatrixRef xResMat;
3777 double nMin = ::std::numeric_limits<double>::max();
3778 auto MatOpFunc = [&xResMat]( SCSIZE i, double fCurMin )
3780 double fVecRes = xResMat->GetDouble(0,i);
3781 if (fVecRes > fCurMin)
3782 xResMat->PutDouble( fCurMin, 0,i);
3784 const SCSIZE nMatRows = GetRefListArrayMaxSize( nParamCount);
3785 size_t nRefArrayPos = std::numeric_limits<size_t>::max();
3787 double nVal = 0.0;
3788 ScAddress aAdr;
3789 ScRange aRange;
3790 size_t nRefInList = 0;
3791 while (nParamCount-- > 0)
3793 switch (GetStackType())
3795 case svDouble :
3797 nVal = GetDouble();
3798 if (nMin > nVal) nMin = nVal;
3799 nFuncFmtType = SvNumFormatType::NUMBER;
3801 break;
3802 case svSingleRef :
3804 PopSingleRef( aAdr );
3805 ScRefCellValue aCell(mrDoc, aAdr);
3806 if (aCell.hasNumeric())
3808 nVal = GetCellValue(aAdr, aCell);
3809 CurFmtToFuncFmt();
3810 if (nMin > nVal) nMin = nVal;
3812 else if (bTextAsZero && aCell.hasString())
3814 if ( nMin > 0.0 )
3815 nMin = 0.0;
3818 break;
3819 case svRefList :
3821 // bDoMatOp only for non-array value when switching to
3822 // ArrayRefList.
3823 if (SwitchToArrayRefList( xResMat, nMatRows, nMin, MatOpFunc,
3824 nRefArrayPos == std::numeric_limits<size_t>::max()))
3826 nRefArrayPos = nRefInList;
3829 [[fallthrough]];
3830 case svDoubleRef :
3832 FormulaError nErr = FormulaError::NONE;
3833 PopDoubleRef( aRange, nParamCount, nRefInList);
3834 ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero );
3835 if (aValIter.GetFirst(nVal, nErr))
3837 if (nMin > nVal)
3838 nMin = nVal;
3839 aValIter.GetCurNumFmtInfo( nFuncFmtType, nFuncFmtIndex );
3840 while ((nErr == FormulaError::NONE) && aValIter.GetNext(nVal, nErr))
3842 if (nMin > nVal)
3843 nMin = nVal;
3845 SetError(nErr);
3847 if (nRefArrayPos != std::numeric_limits<size_t>::max())
3849 // Update vector element with current value.
3850 MatOpFunc( nRefArrayPos, nMin);
3852 // Reset.
3853 nMin = std::numeric_limits<double>::max();
3854 nVal = 0.0;
3855 nRefArrayPos = std::numeric_limits<size_t>::max();
3858 break;
3859 case svMatrix :
3860 case svExternalSingleRef:
3861 case svExternalDoubleRef:
3863 ScMatrixRef pMat = GetMatrix();
3864 if (pMat)
3866 nFuncFmtType = SvNumFormatType::NUMBER;
3867 nVal = pMat->GetMinValue(bTextAsZero, bool(mnSubTotalFlags & SubtotalFlags::IgnoreErrVal));
3868 if (nMin > nVal)
3869 nMin = nVal;
3872 break;
3873 case svString :
3875 Pop();
3876 if ( bTextAsZero )
3878 if ( nMin > 0.0 )
3879 nMin = 0.0;
3881 else
3882 SetError(FormulaError::IllegalParameter);
3884 break;
3885 default :
3886 PopError();
3887 SetError(FormulaError::IllegalParameter);
3891 if (xResMat)
3893 // Include value of last non-references-array type and calculate final result.
3894 if (nMin < std::numeric_limits<double>::max())
3896 for (SCSIZE i=0; i < nMatRows; ++i)
3898 MatOpFunc( i, nMin);
3901 else
3903 /* TODO: the awkward "no value is minimum 0.0" is likely the case
3904 * if a value is numeric_limits::max. Still, that could be a valid
3905 * minimum value as well, but nVal and nMin had been reset after
3906 * the last svRefList... so we may lie here. */
3907 for (SCSIZE i=0; i < nMatRows; ++i)
3909 double fVecRes = xResMat->GetDouble(0,i);
3910 if (fVecRes == std::numeric_limits<double>::max())
3911 xResMat->PutDouble( 0.0, 0,i);
3914 PushMatrix( xResMat);
3916 else
3918 if (!std::isfinite(nVal))
3919 PushError( GetDoubleErrorValue( nVal));
3920 else if ( nVal < nMin )
3921 PushDouble(0.0); // zero or only empty arguments
3922 else
3923 PushDouble(nMin);
3927 void ScInterpreter::ScMax( bool bTextAsZero )
3929 short nParamCount = GetByte();
3930 if (!MustHaveParamCountMin( nParamCount, 1))
3931 return;
3933 ScMatrixRef xResMat;
3934 double nMax = std::numeric_limits<double>::lowest();
3935 auto MatOpFunc = [&xResMat]( SCSIZE i, double fCurMax )
3937 double fVecRes = xResMat->GetDouble(0,i);
3938 if (fVecRes < fCurMax)
3939 xResMat->PutDouble( fCurMax, 0,i);
3941 const SCSIZE nMatRows = GetRefListArrayMaxSize( nParamCount);
3942 size_t nRefArrayPos = std::numeric_limits<size_t>::max();
3944 double nVal = 0.0;
3945 ScAddress aAdr;
3946 ScRange aRange;
3947 size_t nRefInList = 0;
3948 while (nParamCount-- > 0)
3950 switch (GetStackType())
3952 case svDouble :
3954 nVal = GetDouble();
3955 if (nMax < nVal) nMax = nVal;
3956 nFuncFmtType = SvNumFormatType::NUMBER;
3958 break;
3959 case svSingleRef :
3961 PopSingleRef( aAdr );
3962 ScRefCellValue aCell(mrDoc, aAdr);
3963 if (aCell.hasNumeric())
3965 nVal = GetCellValue(aAdr, aCell);
3966 CurFmtToFuncFmt();
3967 if (nMax < nVal) nMax = nVal;
3969 else if (bTextAsZero && aCell.hasString())
3971 if ( nMax < 0.0 )
3972 nMax = 0.0;
3975 break;
3976 case svRefList :
3978 // bDoMatOp only for non-array value when switching to
3979 // ArrayRefList.
3980 if (SwitchToArrayRefList( xResMat, nMatRows, nMax, MatOpFunc,
3981 nRefArrayPos == std::numeric_limits<size_t>::max()))
3983 nRefArrayPos = nRefInList;
3986 [[fallthrough]];
3987 case svDoubleRef :
3989 FormulaError nErr = FormulaError::NONE;
3990 PopDoubleRef( aRange, nParamCount, nRefInList);
3991 ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero );
3992 if (aValIter.GetFirst(nVal, nErr))
3994 if (nMax < nVal)
3995 nMax = nVal;
3996 aValIter.GetCurNumFmtInfo( nFuncFmtType, nFuncFmtIndex );
3997 while ((nErr == FormulaError::NONE) && aValIter.GetNext(nVal, nErr))
3999 if (nMax < nVal)
4000 nMax = nVal;
4002 SetError(nErr);
4004 if (nRefArrayPos != std::numeric_limits<size_t>::max())
4006 // Update vector element with current value.
4007 MatOpFunc( nRefArrayPos, nMax);
4009 // Reset.
4010 nMax = std::numeric_limits<double>::lowest();
4011 nVal = 0.0;
4012 nRefArrayPos = std::numeric_limits<size_t>::max();
4015 break;
4016 case svMatrix :
4017 case svExternalSingleRef:
4018 case svExternalDoubleRef:
4020 ScMatrixRef pMat = GetMatrix();
4021 if (pMat)
4023 nFuncFmtType = SvNumFormatType::NUMBER;
4024 nVal = pMat->GetMaxValue(bTextAsZero, bool(mnSubTotalFlags & SubtotalFlags::IgnoreErrVal));
4025 if (nMax < nVal)
4026 nMax = nVal;
4029 break;
4030 case svString :
4032 Pop();
4033 if ( bTextAsZero )
4035 if ( nMax < 0.0 )
4036 nMax = 0.0;
4038 else
4039 SetError(FormulaError::IllegalParameter);
4041 break;
4042 default :
4043 PopError();
4044 SetError(FormulaError::IllegalParameter);
4048 if (xResMat)
4050 // Include value of last non-references-array type and calculate final result.
4051 if (nMax > std::numeric_limits<double>::lowest())
4053 for (SCSIZE i=0; i < nMatRows; ++i)
4055 MatOpFunc( i, nMax);
4058 else
4060 /* TODO: the awkward "no value is maximum 0.0" is likely the case
4061 * if a value is numeric_limits::lowest. Still, that could be a
4062 * valid maximum value as well, but nVal and nMax had been reset
4063 * after the last svRefList... so we may lie here. */
4064 for (SCSIZE i=0; i < nMatRows; ++i)
4066 double fVecRes = xResMat->GetDouble(0,i);
4067 if (fVecRes == -std::numeric_limits<double>::max())
4068 xResMat->PutDouble( 0.0, 0,i);
4071 PushMatrix( xResMat);
4073 else
4075 if (!std::isfinite(nVal))
4076 PushError( GetDoubleErrorValue( nVal));
4077 else if ( nVal > nMax )
4078 PushDouble(0.0); // zero or only empty arguments
4079 else
4080 PushDouble(nMax);
4084 void ScInterpreter::GetStVarParams( bool bTextAsZero, double(*VarResult)( double fVal, size_t nValCount ) )
4086 short nParamCount = GetByte();
4087 const SCSIZE nMatRows = GetRefListArrayMaxSize( nParamCount);
4089 struct ArrayRefListValue
4091 std::vector<double> mvValues;
4092 KahanSum mfSum;
4093 ArrayRefListValue() = default;
4094 double get() const { return mfSum.get(); }
4096 std::vector<ArrayRefListValue> vArrayValues;
4098 std::vector<double> values;
4099 KahanSum fSum = 0.0;
4100 double fVal = 0.0;
4101 ScAddress aAdr;
4102 ScRange aRange;
4103 size_t nRefInList = 0;
4104 while (nGlobalError == FormulaError::NONE && nParamCount-- > 0)
4106 switch (GetStackType())
4108 case svDouble :
4110 fVal = GetDouble();
4111 if (nGlobalError == FormulaError::NONE)
4113 values.push_back(fVal);
4114 fSum += fVal;
4117 break;
4118 case svSingleRef :
4120 PopSingleRef( aAdr );
4121 ScRefCellValue aCell(mrDoc, aAdr);
4122 if (aCell.hasNumeric())
4124 fVal = GetCellValue(aAdr, aCell);
4125 if (nGlobalError == FormulaError::NONE)
4127 values.push_back(fVal);
4128 fSum += fVal;
4131 else if (bTextAsZero && aCell.hasString())
4133 values.push_back(0.0);
4136 break;
4137 case svRefList :
4139 const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]);
4140 if (p && p->IsArrayResult())
4142 size_t nRefArrayPos = nRefInList;
4143 if (vArrayValues.empty())
4145 // Create and init all elements with current value.
4146 assert(nMatRows > 0);
4147 vArrayValues.resize(nMatRows);
4148 for (ArrayRefListValue & it : vArrayValues)
4150 it.mvValues = values;
4151 it.mfSum = fSum;
4154 else
4156 // Current value and values from vector are operands
4157 // for each vector position.
4158 for (ArrayRefListValue & it : vArrayValues)
4160 it.mvValues.insert( it.mvValues.end(), values.begin(), values.end());
4161 it.mfSum += fSum;
4164 ArrayRefListValue& rArrayValue = vArrayValues[nRefArrayPos];
4165 FormulaError nErr = FormulaError::NONE;
4166 PopDoubleRef( aRange, nParamCount, nRefInList);
4167 ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero );
4168 if (aValIter.GetFirst(fVal, nErr))
4172 rArrayValue.mvValues.push_back(fVal);
4173 rArrayValue.mfSum += fVal;
4175 while ((nErr == FormulaError::NONE) && aValIter.GetNext(fVal, nErr));
4177 if ( nErr != FormulaError::NONE )
4179 rArrayValue.mfSum = CreateDoubleError( nErr);
4181 // Reset.
4182 std::vector<double>().swap(values);
4183 fSum = 0.0;
4184 break;
4187 [[fallthrough]];
4188 case svDoubleRef :
4190 FormulaError nErr = FormulaError::NONE;
4191 PopDoubleRef( aRange, nParamCount, nRefInList);
4192 ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero );
4193 if (aValIter.GetFirst(fVal, nErr))
4197 values.push_back(fVal);
4198 fSum += fVal;
4200 while ((nErr == FormulaError::NONE) && aValIter.GetNext(fVal, nErr));
4202 if ( nErr != FormulaError::NONE )
4204 SetError(nErr);
4207 break;
4208 case svExternalSingleRef :
4209 case svExternalDoubleRef :
4210 case svMatrix :
4212 ScMatrixRef pMat = GetMatrix();
4213 if (pMat)
4215 const bool bIgnoreErrVal = bool(mnSubTotalFlags & SubtotalFlags::IgnoreErrVal);
4216 SCSIZE nC, nR;
4217 pMat->GetDimensions(nC, nR);
4218 for (SCSIZE nMatCol = 0; nMatCol < nC; nMatCol++)
4220 for (SCSIZE nMatRow = 0; nMatRow < nR; nMatRow++)
4222 if (!pMat->IsStringOrEmpty(nMatCol,nMatRow))
4224 fVal= pMat->GetDouble(nMatCol,nMatRow);
4225 if (nGlobalError == FormulaError::NONE)
4227 values.push_back(fVal);
4228 fSum += fVal;
4230 else if (bIgnoreErrVal)
4231 nGlobalError = FormulaError::NONE;
4233 else if ( bTextAsZero )
4235 values.push_back(0.0);
4241 break;
4242 case svString :
4244 Pop();
4245 if ( bTextAsZero )
4247 values.push_back(0.0);
4249 else
4250 SetError(FormulaError::IllegalParameter);
4252 break;
4253 default :
4254 PopError();
4255 SetError(FormulaError::IllegalParameter);
4259 if (!vArrayValues.empty())
4261 // Include value of last non-references-array type and calculate final result.
4262 if (!values.empty())
4264 for (auto & it : vArrayValues)
4266 it.mvValues.insert( it.mvValues.end(), values.begin(), values.end());
4267 it.mfSum += fSum;
4270 ScMatrixRef xResMat = GetNewMat( 1, nMatRows, true);
4271 for (SCSIZE r=0; r < nMatRows; ++r)
4273 ::std::vector<double>::size_type n = vArrayValues[r].mvValues.size();
4274 if (!n)
4275 xResMat->PutError( FormulaError::DivisionByZero, 0, r);
4276 else
4278 ArrayRefListValue& rArrayValue = vArrayValues[r];
4279 double vSum = 0.0;
4280 const double vMean = rArrayValue.get() / n;
4281 for (::std::vector<double>::size_type i = 0; i < n; i++)
4282 vSum += ::rtl::math::approxSub( rArrayValue.mvValues[i], vMean) *
4283 ::rtl::math::approxSub( rArrayValue.mvValues[i], vMean);
4284 xResMat->PutDouble( VarResult( vSum, n), 0, r);
4287 PushMatrix( xResMat);
4289 else
4291 ::std::vector<double>::size_type n = values.size();
4292 if (!n)
4293 SetError( FormulaError::DivisionByZero);
4294 double vSum = 0.0;
4295 if (nGlobalError == FormulaError::NONE)
4297 const double vMean = fSum.get() / n;
4298 for (::std::vector<double>::size_type i = 0; i < n; i++)
4299 vSum += ::rtl::math::approxSub( values[i], vMean) * ::rtl::math::approxSub( values[i], vMean);
4301 PushDouble( VarResult( vSum, n));
4305 void ScInterpreter::ScVar( bool bTextAsZero )
4307 auto VarResult = []( double fVal, size_t nValCount )
4309 if (nValCount <= 1)
4310 return CreateDoubleError( FormulaError::DivisionByZero );
4311 else
4312 return fVal / (nValCount - 1);
4314 GetStVarParams( bTextAsZero, VarResult );
4317 void ScInterpreter::ScVarP( bool bTextAsZero )
4319 auto VarResult = []( double fVal, size_t nValCount )
4321 return sc::div( fVal, nValCount);
4323 GetStVarParams( bTextAsZero, VarResult );
4327 void ScInterpreter::ScStDev( bool bTextAsZero )
4329 auto VarResult = []( double fVal, size_t nValCount )
4331 if (nValCount <= 1)
4332 return CreateDoubleError( FormulaError::DivisionByZero );
4333 else
4334 return sqrt( fVal / (nValCount - 1));
4336 GetStVarParams( bTextAsZero, VarResult );
4339 void ScInterpreter::ScStDevP( bool bTextAsZero )
4341 auto VarResult = []( double fVal, size_t nValCount )
4343 if (nValCount == 0)
4344 return CreateDoubleError( FormulaError::DivisionByZero );
4345 else
4346 return sqrt( fVal / nValCount);
4348 GetStVarParams( bTextAsZero, VarResult );
4350 /* this was: PushDouble( sqrt( div( nVal, nValCount)));
4352 * Besides that the special NAN gets lost in the call through sqrt(),
4353 * unxlngi6.pro then looped back and forth somewhere between div() and
4354 * ::rtl::math::setNan(). Tests showed that
4356 * sqrt( div( 1, 0));
4358 * produced a loop, but
4360 * double f1 = div( 1, 0);
4361 * sqrt( f1 );
4363 * was fine. There seems to be some compiler optimization problem. It does
4364 * not occur when compiled with debug=t.
4368 void ScInterpreter::ScColumns()
4370 sal_uInt8 nParamCount = GetByte();
4371 sal_uLong nVal = 0;
4372 SCCOL nCol1;
4373 SCROW nRow1;
4374 SCTAB nTab1;
4375 SCCOL nCol2;
4376 SCROW nRow2;
4377 SCTAB nTab2;
4378 while (nParamCount-- > 0)
4380 switch ( GetStackType() )
4382 case svSingleRef:
4383 PopError();
4384 nVal++;
4385 break;
4386 case svDoubleRef:
4387 PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
4388 nVal += static_cast<sal_uLong>(nTab2 - nTab1 + 1) *
4389 static_cast<sal_uLong>(nCol2 - nCol1 + 1);
4390 break;
4391 case svMatrix:
4393 ScMatrixRef pMat = PopMatrix();
4394 if (pMat)
4396 SCSIZE nC, nR;
4397 pMat->GetDimensions(nC, nR);
4398 nVal += nC;
4401 break;
4402 case svExternalSingleRef:
4403 PopError();
4404 nVal++;
4405 break;
4406 case svExternalDoubleRef:
4408 sal_uInt16 nFileId;
4409 OUString aTabName;
4410 ScComplexRefData aRef;
4411 PopExternalDoubleRef( nFileId, aTabName, aRef);
4412 ScRange aAbs = aRef.toAbs(mrDoc, aPos);
4413 nVal += static_cast<sal_uLong>(aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1) *
4414 static_cast<sal_uLong>(aAbs.aEnd.Col() - aAbs.aStart.Col() + 1);
4416 break;
4417 default:
4418 PopError();
4419 SetError(FormulaError::IllegalParameter);
4422 PushDouble(static_cast<double>(nVal));
4425 void ScInterpreter::ScRows()
4427 sal_uInt8 nParamCount = GetByte();
4428 sal_uLong nVal = 0;
4429 SCCOL nCol1;
4430 SCROW nRow1;
4431 SCTAB nTab1;
4432 SCCOL nCol2;
4433 SCROW nRow2;
4434 SCTAB nTab2;
4435 while (nParamCount-- > 0)
4437 switch ( GetStackType() )
4439 case svSingleRef:
4440 PopError();
4441 nVal++;
4442 break;
4443 case svDoubleRef:
4444 PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
4445 nVal += static_cast<sal_uLong>(nTab2 - nTab1 + 1) *
4446 static_cast<sal_uLong>(nRow2 - nRow1 + 1);
4447 break;
4448 case svMatrix:
4450 ScMatrixRef pMat = PopMatrix();
4451 if (pMat)
4453 SCSIZE nC, nR;
4454 pMat->GetDimensions(nC, nR);
4455 nVal += nR;
4458 break;
4459 case svExternalSingleRef:
4460 PopError();
4461 nVal++;
4462 break;
4463 case svExternalDoubleRef:
4465 sal_uInt16 nFileId;
4466 OUString aTabName;
4467 ScComplexRefData aRef;
4468 PopExternalDoubleRef( nFileId, aTabName, aRef);
4469 ScRange aAbs = aRef.toAbs(mrDoc, aPos);
4470 nVal += static_cast<sal_uLong>(aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1) *
4471 static_cast<sal_uLong>(aAbs.aEnd.Row() - aAbs.aStart.Row() + 1);
4473 break;
4474 default:
4475 PopError();
4476 SetError(FormulaError::IllegalParameter);
4479 PushDouble(static_cast<double>(nVal));
4482 void ScInterpreter::ScSheets()
4484 sal_uInt8 nParamCount = GetByte();
4485 sal_uLong nVal;
4486 if ( nParamCount == 0 )
4487 nVal = mrDoc.GetTableCount();
4488 else
4490 nVal = 0;
4491 SCCOL nCol1;
4492 SCROW nRow1;
4493 SCTAB nTab1;
4494 SCCOL nCol2;
4495 SCROW nRow2;
4496 SCTAB nTab2;
4497 while (nGlobalError == FormulaError::NONE && nParamCount-- > 0)
4499 switch ( GetStackType() )
4501 case svSingleRef:
4502 case svExternalSingleRef:
4503 PopError();
4504 nVal++;
4505 break;
4506 case svDoubleRef:
4507 PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
4508 nVal += static_cast<sal_uLong>(nTab2 - nTab1 + 1);
4509 break;
4510 case svExternalDoubleRef:
4512 sal_uInt16 nFileId;
4513 OUString aTabName;
4514 ScComplexRefData aRef;
4515 PopExternalDoubleRef( nFileId, aTabName, aRef);
4516 ScRange aAbs = aRef.toAbs(mrDoc, aPos);
4517 nVal += static_cast<sal_uLong>(aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1);
4519 break;
4520 default:
4521 PopError();
4522 SetError( FormulaError::IllegalParameter );
4526 PushDouble( static_cast<double>(nVal) );
4529 void ScInterpreter::ScColumn()
4531 sal_uInt8 nParamCount = GetByte();
4532 if ( !MustHaveParamCount( nParamCount, 0, 1 ) )
4533 return;
4535 double nVal = 0.0;
4536 if (nParamCount == 0)
4538 nVal = aPos.Col() + 1;
4539 if (bMatrixFormula)
4541 SCCOL nCols = 0;
4542 SCROW nRows = 0;
4543 if (pMyFormulaCell)
4544 pMyFormulaCell->GetMatColsRows( nCols, nRows);
4545 bool bMayBeScalar;
4546 if (nCols == 0)
4548 // Happens if called via ScViewFunc::EnterMatrix()
4549 // ScFormulaCell::GetResultDimensions() as of course a
4550 // matrix result is not available yet.
4551 nCols = 1;
4552 bMayBeScalar = false;
4554 else
4556 bMayBeScalar = true;
4558 if (!bMayBeScalar || nCols != 1 || nRows != 1)
4560 ScMatrixRef pResMat = GetNewMat( static_cast<SCSIZE>(nCols), 1, /*bEmpty*/true );
4561 if (pResMat)
4563 for (SCCOL i=0; i < nCols; ++i)
4564 pResMat->PutDouble( nVal + i, static_cast<SCSIZE>(i), 0);
4565 PushMatrix( pResMat);
4566 return;
4571 else
4573 switch ( GetStackType() )
4575 case svSingleRef :
4577 SCCOL nCol1(0);
4578 SCROW nRow1(0);
4579 SCTAB nTab1(0);
4580 PopSingleRef( nCol1, nRow1, nTab1 );
4581 nVal = static_cast<double>(nCol1 + 1);
4583 break;
4584 case svExternalSingleRef :
4586 sal_uInt16 nFileId;
4587 OUString aTabName;
4588 ScSingleRefData aRef;
4589 PopExternalSingleRef( nFileId, aTabName, aRef );
4590 ScAddress aAbsRef = aRef.toAbs(mrDoc, aPos);
4591 nVal = static_cast<double>( aAbsRef.Col() + 1 );
4593 break;
4595 case svDoubleRef :
4596 case svExternalDoubleRef :
4598 SCCOL nCol1;
4599 SCCOL nCol2;
4600 if ( GetStackType() == svDoubleRef )
4602 SCROW nRow1;
4603 SCTAB nTab1;
4604 SCROW nRow2;
4605 SCTAB nTab2;
4606 PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
4608 else
4610 sal_uInt16 nFileId;
4611 OUString aTabName;
4612 ScComplexRefData aRef;
4613 PopExternalDoubleRef( nFileId, aTabName, aRef );
4614 ScRange aAbs = aRef.toAbs(mrDoc, aPos);
4615 nCol1 = aAbs.aStart.Col();
4616 nCol2 = aAbs.aEnd.Col();
4618 if (nCol2 > nCol1)
4620 ScMatrixRef pResMat = GetNewMat(
4621 static_cast<SCSIZE>(nCol2-nCol1+1), 1, /*bEmpty*/true);
4622 if (pResMat)
4624 for (SCCOL i = nCol1; i <= nCol2; i++)
4625 pResMat->PutDouble(static_cast<double>(i+1),
4626 static_cast<SCSIZE>(i-nCol1), 0);
4627 PushMatrix(pResMat);
4628 return;
4631 else
4632 nVal = static_cast<double>(nCol1 + 1);
4634 break;
4635 default:
4636 SetError( FormulaError::IllegalParameter );
4639 PushDouble( nVal );
4642 void ScInterpreter::ScRow()
4644 sal_uInt8 nParamCount = GetByte();
4645 if ( !MustHaveParamCount( nParamCount, 0, 1 ) )
4646 return;
4648 double nVal = 0.0;
4649 if (nParamCount == 0)
4651 nVal = aPos.Row() + 1;
4652 if (bMatrixFormula)
4654 SCCOL nCols = 0;
4655 SCROW nRows = 0;
4656 if (pMyFormulaCell)
4657 pMyFormulaCell->GetMatColsRows( nCols, nRows);
4658 bool bMayBeScalar;
4659 if (nRows == 0)
4661 // Happens if called via ScViewFunc::EnterMatrix()
4662 // ScFormulaCell::GetResultDimensions() as of course a
4663 // matrix result is not available yet.
4664 nRows = 1;
4665 bMayBeScalar = false;
4667 else
4669 bMayBeScalar = true;
4671 if (!bMayBeScalar || nCols != 1 || nRows != 1)
4673 ScMatrixRef pResMat = GetNewMat( 1, static_cast<SCSIZE>(nRows), /*bEmpty*/true);
4674 if (pResMat)
4676 for (SCROW i=0; i < nRows; i++)
4677 pResMat->PutDouble( nVal + i, 0, static_cast<SCSIZE>(i));
4678 PushMatrix( pResMat);
4679 return;
4684 else
4686 switch ( GetStackType() )
4688 case svSingleRef :
4690 SCCOL nCol1(0);
4691 SCROW nRow1(0);
4692 SCTAB nTab1(0);
4693 PopSingleRef( nCol1, nRow1, nTab1 );
4694 nVal = static_cast<double>(nRow1 + 1);
4696 break;
4697 case svExternalSingleRef :
4699 sal_uInt16 nFileId;
4700 OUString aTabName;
4701 ScSingleRefData aRef;
4702 PopExternalSingleRef( nFileId, aTabName, aRef );
4703 ScAddress aAbsRef = aRef.toAbs(mrDoc, aPos);
4704 nVal = static_cast<double>( aAbsRef.Row() + 1 );
4706 break;
4707 case svDoubleRef :
4708 case svExternalDoubleRef :
4710 SCROW nRow1;
4711 SCROW nRow2;
4712 if ( GetStackType() == svDoubleRef )
4714 SCCOL nCol1;
4715 SCTAB nTab1;
4716 SCCOL nCol2;
4717 SCTAB nTab2;
4718 PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
4720 else
4722 sal_uInt16 nFileId;
4723 OUString aTabName;
4724 ScComplexRefData aRef;
4725 PopExternalDoubleRef( nFileId, aTabName, aRef );
4726 ScRange aAbs = aRef.toAbs(mrDoc, aPos);
4727 nRow1 = aAbs.aStart.Row();
4728 nRow2 = aAbs.aEnd.Row();
4730 if (nRow2 > nRow1)
4732 ScMatrixRef pResMat = GetNewMat( 1,
4733 static_cast<SCSIZE>(nRow2-nRow1+1), /*bEmpty*/true);
4734 if (pResMat)
4736 for (SCROW i = nRow1; i <= nRow2; i++)
4737 pResMat->PutDouble(static_cast<double>(i+1), 0,
4738 static_cast<SCSIZE>(i-nRow1));
4739 PushMatrix(pResMat);
4740 return;
4743 else
4744 nVal = static_cast<double>(nRow1 + 1);
4746 break;
4747 default:
4748 SetError( FormulaError::IllegalParameter );
4751 PushDouble( nVal );
4754 void ScInterpreter::ScSheet()
4756 sal_uInt8 nParamCount = GetByte();
4757 if ( !MustHaveParamCount( nParamCount, 0, 1 ) )
4758 return;
4760 SCTAB nVal = 0;
4761 if ( nParamCount == 0 )
4762 nVal = aPos.Tab() + 1;
4763 else
4765 switch ( GetStackType() )
4767 case svString :
4769 svl::SharedString aStr = PopString();
4770 if ( mrDoc.GetTable(aStr.getString(), nVal))
4771 ++nVal;
4772 else
4773 SetError( FormulaError::IllegalArgument );
4775 break;
4776 case svSingleRef :
4778 SCCOL nCol1(0);
4779 SCROW nRow1(0);
4780 SCTAB nTab1(0);
4781 PopSingleRef(nCol1, nRow1, nTab1);
4782 nVal = nTab1 + 1;
4784 break;
4785 case svDoubleRef :
4787 SCCOL nCol1;
4788 SCROW nRow1;
4789 SCTAB nTab1;
4790 SCCOL nCol2;
4791 SCROW nRow2;
4792 SCTAB nTab2;
4793 PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
4794 nVal = nTab1 + 1;
4796 break;
4797 default:
4798 SetError( FormulaError::IllegalParameter );
4800 if ( nGlobalError != FormulaError::NONE )
4801 nVal = 0;
4803 PushDouble( static_cast<double>(nVal) );
4806 namespace {
4808 class VectorMatrixAccessor
4810 public:
4811 VectorMatrixAccessor(const ScMatrix& rMat, bool bColVec) :
4812 mrMat(rMat), mbColVec(bColVec) {}
4814 bool IsEmpty(SCSIZE i) const
4816 return mbColVec ? mrMat.IsEmpty(0, i) : mrMat.IsEmpty(i, 0);
4819 bool IsEmptyPath(SCSIZE i) const
4821 return mbColVec ? mrMat.IsEmptyPath(0, i) : mrMat.IsEmptyPath(i, 0);
4824 bool IsValue(SCSIZE i) const
4826 return mbColVec ? mrMat.IsValue(0, i) : mrMat.IsValue(i, 0);
4829 bool IsStringOrEmpty(SCSIZE i) const
4831 return mbColVec ? mrMat.IsStringOrEmpty(0, i) : mrMat.IsStringOrEmpty(i, 0);
4834 double GetDouble(SCSIZE i) const
4836 return mbColVec ? mrMat.GetDouble(0, i) : mrMat.GetDouble(i, 0);
4839 OUString GetString(SCSIZE i) const
4841 return mbColVec ? mrMat.GetString(0, i).getString() : mrMat.GetString(i, 0).getString();
4844 SCSIZE GetElementCount() const
4846 SCSIZE nC, nR;
4847 mrMat.GetDimensions(nC, nR);
4848 return mbColVec ? nR : nC;
4851 private:
4852 const ScMatrix& mrMat;
4853 bool mbColVec;
4856 /** returns -1 when the matrix value is smaller than the query value, 0 when
4857 they are equal, and 1 when the matrix value is larger than the query
4858 value. */
4859 sal_Int32 lcl_CompareMatrix2Query(
4860 SCSIZE i, const VectorMatrixAccessor& rMat, const ScQueryParam& rParam, const ScQueryEntry& rEntry, bool bMatchWholeCell )
4862 if (rMat.IsEmpty(i))
4864 /* TODO: in case we introduced query for real empty this would have to
4865 * be changed! */
4866 return -1; // empty always less than anything else
4869 /* FIXME: what is an empty path (result of IF(false;true_path) in
4870 * comparisons? */
4872 bool bByString = rEntry.GetQueryItem().meType == ScQueryEntry::ByString;
4873 if (rMat.IsValue(i))
4875 const double nVal1 = rMat.GetDouble(i);
4876 if (!std::isfinite(nVal1))
4878 // XXX Querying for error values is not required, otherwise we'd
4879 // need to check here.
4880 return 1; // error always greater than numeric or string
4883 if (bByString)
4884 return -1; // numeric always less than string
4886 const double nVal2 = rEntry.GetQueryItem().mfVal;
4887 // XXX Querying for error values is not required, otherwise we'd need
4888 // to check here and move that check before the bByString check.
4889 if (nVal1 == nVal2)
4890 return 0;
4892 return nVal1 < nVal2 ? -1 : 1;
4895 if (!bByString)
4896 return 1; // string always greater than numeric
4898 OUString aStr1 = rMat.GetString(i);
4899 OUString aStr2 = rEntry.GetQueryItem().maString.getString();
4901 // bRealWildOrRegExp
4902 if (rParam.eSearchType != utl::SearchParam::SearchType::Normal &&
4903 ((rEntry.eOp == SC_EQUAL) || (rEntry.eOp == SC_NOT_EQUAL)))
4905 sal_Int32 nStart = 0;
4906 sal_Int32 nEnd = aStr1.getLength();
4908 bool bMatch = rEntry.GetSearchTextPtr(rParam.eSearchType, rParam.bCaseSens, bMatchWholeCell)
4909 ->SearchForward(aStr1, &nStart, &nEnd);
4910 // from 614 on, nEnd is behind the found text
4911 if (bMatch && bMatchWholeCell
4912 && (nStart != 0 || nEnd != aStr1.getLength()))
4913 bMatch = false; // RegExp must match entire cell string
4915 bool bOk = ((rEntry.eOp == SC_NOT_EQUAL) ? !bMatch : bMatch);
4917 if (bOk)
4918 return 0; // we have a WildOrRegExp match
4921 CollatorWrapper& rCollator = ScGlobal::GetCollator(rParam.bCaseSens);
4922 return rCollator.compareString(aStr1, aStr2);
4925 /** returns -1 when matrix(i) value is smaller than matrix(j) value, 0 when
4926 they are equal, and 1 when larger */
4927 sal_Int32 lcl_Compare2MatrixCells( SCSIZE i, const VectorMatrixAccessor& rMat, SCSIZE j )
4929 // empty always less than anything else
4930 if (rMat.IsEmpty(i))
4931 return ( rMat.IsEmpty(j) ? 0 : -1 );
4932 else if (rMat.IsEmpty(j))
4933 return 1;
4935 bool bByString = rMat.IsStringOrEmpty(j); // string, empty has already been handled
4936 if (rMat.IsValue(i))
4938 const double nVal1 = rMat.GetDouble(i);
4939 if (!std::isfinite(nVal1))
4940 return 1; // error always greater than numeric or string
4942 if (bByString)
4943 return -1; // numeric always less than string
4945 const double nVal2 = rMat.GetDouble(j);
4946 if (nVal1 == nVal2)
4947 return 0;
4949 return ( nVal1 < nVal2 ? -1 : 1 );
4952 if (!bByString)
4953 return 1; // string always greater than numeric
4955 return ScGlobal::GetCollator().compareString(rMat.GetString(i), rMat.GetString(j)); // case-insensitive
4958 /** returns the last item with the identical value as the original item
4959 value. */
4960 void lcl_GetLastMatch( SCSIZE& rIndex, const VectorMatrixAccessor& rMat,
4961 SCSIZE nMatCount)
4963 if (rMat.IsValue(rIndex))
4965 double nVal = rMat.GetDouble(rIndex);
4966 while (rIndex < nMatCount-1 && rMat.IsValue(rIndex+1) &&
4967 nVal == rMat.GetDouble(rIndex+1))
4968 ++rIndex;
4970 // Order of IsEmptyPath, IsEmpty, IsStringOrEmpty is significant!
4971 else if (rMat.IsEmptyPath(rIndex))
4973 while (rIndex < nMatCount-1 && rMat.IsEmptyPath(rIndex+1))
4974 ++rIndex;
4976 else if (rMat.IsEmpty(rIndex))
4978 while (rIndex < nMatCount-1 && rMat.IsEmpty(rIndex+1))
4979 ++rIndex;
4981 else if (rMat.IsStringOrEmpty(rIndex))
4983 OUString aStr( rMat.GetString(rIndex));
4984 while (rIndex < nMatCount-1 && rMat.IsStringOrEmpty(rIndex+1) &&
4985 aStr == rMat.GetString(rIndex+1))
4986 ++rIndex;
4988 else
4990 OSL_FAIL("lcl_GetLastMatch: unhandled matrix type");
4996 void ScInterpreter::ScMatch()
4998 sal_uInt8 nParamCount = GetByte();
4999 if ( !MustHaveParamCount( nParamCount, 2, 3 ) )
5000 return;
5002 VectorSearchArguments vsa;
5003 vsa.nSearchOpCode = SC_OPCODE_MATCH;
5005 // get match mode
5006 double fType = ( nParamCount == 3 ? GetDouble() : 1.0 );
5007 switch ( static_cast<int>(fType) )
5009 case -1 :
5010 vsa.eMatchMode = exactorG;
5011 vsa.eSearchMode = searchbdesc;
5012 break;
5013 case 0 :
5014 vsa.eMatchMode = exactorNA;
5015 vsa.eSearchMode = searchfwd;
5016 break;
5017 case 1 :
5018 // default value
5019 vsa.eMatchMode = exactorS;
5020 vsa.eSearchMode = searchbasc;
5021 break;
5022 default :
5023 PushIllegalParameter();
5024 return;
5027 // get vector to be searched
5028 switch (GetStackType())
5030 case svSingleRef:
5031 PopSingleRef( vsa.nCol1, vsa.nRow1, vsa.nTab1);
5032 vsa.nCol2 = vsa.nCol1;
5033 vsa.nRow2 = vsa.nRow1;
5034 vsa.pMatSrc = nullptr;
5035 break;
5036 case svDoubleRef:
5038 vsa.pMatSrc = nullptr;
5039 SCTAB nTab2 = 0;
5040 PopDoubleRef(vsa.nCol1, vsa.nRow1, vsa.nTab1, vsa.nCol2, vsa.nRow2, nTab2);
5041 if (vsa.nTab1 != nTab2 || (vsa.nCol1 != vsa.nCol2 && vsa.nRow1 != vsa.nRow2))
5043 PushIllegalParameter();
5044 return;
5047 break;
5048 case svMatrix:
5049 case svExternalDoubleRef:
5051 if (GetStackType() == svMatrix)
5052 vsa.pMatSrc = PopMatrix();
5053 else
5054 PopExternalDoubleRef(vsa.pMatSrc);
5056 if (!vsa.pMatSrc)
5058 PushIllegalParameter();
5059 return;
5062 break;
5063 default:
5064 PushIllegalParameter();
5065 return;
5068 // get search value
5069 if (nGlobalError == FormulaError::NONE)
5071 switch ( GetStackType() )
5073 case svDouble:
5075 vsa.isStringSearch = false;
5076 vsa.fSearchVal = GetDouble();
5078 break;
5079 case svString:
5081 vsa.isStringSearch = true;
5082 vsa.sSearchStr = GetString();
5084 break;
5085 case svDoubleRef :
5086 case svSingleRef :
5088 ScAddress aAdr;
5089 if ( !PopDoubleRefOrSingleRef( aAdr ) )
5091 PushInt(0);
5092 return ;
5094 ScRefCellValue aCell(mrDoc, aAdr);
5095 if (aCell.hasNumeric())
5097 vsa.isStringSearch = false;
5098 vsa.fSearchVal = GetCellValue(aAdr, aCell);
5100 else
5102 vsa.isStringSearch = true;
5103 GetCellString(vsa.sSearchStr, aCell);
5106 break;
5107 case svExternalSingleRef:
5109 ScExternalRefCache::TokenRef pToken;
5110 PopExternalSingleRef(pToken);
5111 if (nGlobalError != FormulaError::NONE)
5113 PushError( nGlobalError);
5114 return;
5116 if (pToken->GetType() == svDouble)
5118 vsa.isStringSearch = false;
5119 vsa.fSearchVal = pToken->GetDouble();
5121 else
5123 vsa.isStringSearch = true;
5124 vsa.sSearchStr = pToken->GetString();
5127 break;
5128 case svExternalDoubleRef:
5129 case svMatrix :
5131 ScMatValType nType = GetDoubleOrStringFromMatrix(
5132 vsa.fSearchVal, vsa.sSearchStr);
5133 vsa.isStringSearch = ScMatrix::IsNonValueType(nType);
5135 break;
5136 default:
5138 PushIllegalParameter();
5139 return;
5143 // execute search
5144 if ( SearchVectorForValue( vsa ) )
5145 PushDouble( vsa.nIndex );
5146 else
5148 if ( vsa.isResultNA )
5149 PushNA();
5150 else
5151 return; // error occurred and has already been pushed
5154 else
5155 PushIllegalParameter();
5158 void ScInterpreter::ScXMatch()
5160 sal_uInt8 nParamCount = GetByte();
5161 if (!MustHaveParamCount(nParamCount, 2, 4))
5162 return;
5164 VectorSearchArguments vsa;
5165 vsa.nSearchOpCode = SC_OPCODE_X_MATCH;
5167 // get search mode
5168 if (nParamCount == 4)
5170 sal_Int16 k = GetInt16();
5171 if (k >= -2 && k <= 2 && k != 0)
5172 vsa.eSearchMode = static_cast<SearchMode>(k);
5173 else
5175 PushIllegalParameter();
5176 return;
5179 else
5180 vsa.eSearchMode = searchfwd;
5182 // get match mode
5183 if (nParamCount >= 3)
5185 sal_Int16 k = GetInt16();
5186 if (k >= -1 && k <= 3)
5187 vsa.eMatchMode = static_cast<MatchMode>(k);
5188 else
5190 PushIllegalParameter();
5191 return;
5194 else
5195 vsa.eMatchMode = exactorNA;
5197 // get vector to be searched
5198 switch (GetStackType())
5200 case svSingleRef:
5202 PopSingleRef(vsa.nCol1, vsa.nRow1, vsa.nTab1);
5203 vsa.nCol2 = vsa.nCol1;
5204 vsa.nRow2 = vsa.nRow1;
5205 vsa.pMatSrc = nullptr;
5207 break;
5208 case svDoubleRef:
5210 vsa.pMatSrc = nullptr;
5211 SCTAB nTab2 = 0;
5212 PopDoubleRef(vsa.nCol1, vsa.nRow1, vsa.nTab1, vsa.nCol2, vsa.nRow2, nTab2);
5213 if (vsa.nTab1 != nTab2 || (vsa.nCol1 != vsa.nCol2 && vsa.nRow1 != vsa.nRow2))
5215 PushIllegalParameter();
5216 return;
5219 break;
5220 case svMatrix:
5221 case svExternalDoubleRef:
5223 if (GetStackType() == svMatrix)
5224 vsa.pMatSrc = PopMatrix();
5225 else
5226 PopExternalDoubleRef(vsa.pMatSrc);
5228 if (!vsa.pMatSrc)
5230 PushIllegalParameter();
5231 return;
5234 break;
5235 default:
5236 PushIllegalParameter();
5237 return;
5240 // get search value
5241 if (nGlobalError == FormulaError::NONE)
5243 switch (GetRawStackType())
5245 case svMissing:
5246 case svEmptyCell:
5248 vsa.isEmptySearch = true;
5249 vsa.isStringSearch = false;
5250 vsa.sSearchStr = GetString();
5252 break;
5253 case svDouble:
5255 vsa.isStringSearch = false;
5256 vsa.fSearchVal = GetDouble();
5258 break;
5259 case svString:
5261 vsa.isStringSearch = true;
5262 vsa.sSearchStr = GetString();
5264 break;
5265 case svDoubleRef:
5266 case svSingleRef:
5268 ScAddress aAdr;
5269 if (!PopDoubleRefOrSingleRef(aAdr))
5271 PushInt(0);
5272 return;
5274 ScRefCellValue aCell(mrDoc, aAdr);
5275 if (aCell.hasNumeric())
5277 vsa.isStringSearch = false;
5278 vsa.fSearchVal = GetCellValue(aAdr, aCell);
5280 else
5282 vsa.isStringSearch = true;
5283 GetCellString(vsa.sSearchStr, aCell);
5286 break;
5287 case svExternalSingleRef:
5289 ScExternalRefCache::TokenRef pToken;
5290 PopExternalSingleRef(pToken);
5291 if (nGlobalError != FormulaError::NONE)
5293 PushError(nGlobalError);
5294 return;
5296 if (pToken->GetType() == svDouble)
5298 vsa.isStringSearch = false;
5299 vsa.fSearchVal = pToken->GetDouble();
5301 else
5303 vsa.isStringSearch = true;
5304 vsa.sSearchStr = pToken->GetString();
5307 break;
5308 case svExternalDoubleRef:
5309 case svMatrix:
5311 ScMatValType nType = GetDoubleOrStringFromMatrix(
5312 vsa.fSearchVal, vsa.sSearchStr);
5313 vsa.isStringSearch = ScMatrix::IsNonValueType(nType);
5315 break;
5316 default:
5318 PushIllegalParameter();
5319 return;
5323 // execute search
5324 if (SearchVectorForValue(vsa))
5325 PushDouble(vsa.nIndex);
5326 else
5328 if (vsa.isResultNA)
5329 PushNA();
5330 else
5331 return; // error occurred and has already been pushed
5334 else
5335 PushIllegalParameter();
5338 namespace {
5340 bool isCellContentEmpty( const ScRefCellValue& rCell )
5342 switch (rCell.getType())
5344 case CELLTYPE_VALUE:
5345 case CELLTYPE_STRING:
5346 case CELLTYPE_EDIT:
5347 return false;
5348 case CELLTYPE_FORMULA:
5350 // NOTE: Excel treats ="" in a referenced cell as blank in
5351 // COUNTBLANK() but not in ISBLANK(), which is inconsistent.
5352 // COUNTBLANK() tests the (display) result whereas ISBLANK() tests
5353 // the cell content.
5354 // ODFF allows both for COUNTBLANK().
5355 // OOo and LibreOffice prior to 4.4 did not treat ="" as blank in
5356 // COUNTBLANK(), we now do for Excel interoperability.
5357 /* TODO: introduce yet another compatibility option? */
5358 sc::FormulaResultValue aRes = rCell.getFormula()->GetResult();
5359 if (aRes.meType != sc::FormulaResultValue::String)
5360 return false;
5361 if (!aRes.maString.isEmpty())
5362 return false;
5364 break;
5365 default:
5369 return true;
5374 void ScInterpreter::ScCountEmptyCells()
5376 if ( !MustHaveParamCount( GetByte(), 1 ) )
5377 return;
5379 const SCSIZE nMatRows = GetRefListArrayMaxSize(1);
5380 // There's either one RefList and nothing else, or none.
5381 ScMatrixRef xResMat = (nMatRows ? GetNewMat( 1, nMatRows, /*bEmpty*/true ) : nullptr);
5382 sal_uLong nMaxCount = 0, nCount = 0;
5383 switch (GetStackType())
5385 case svSingleRef :
5387 nMaxCount = 1;
5388 ScAddress aAdr;
5389 PopSingleRef( aAdr );
5390 ScRefCellValue aCell(mrDoc, aAdr);
5391 if (!isCellContentEmpty(aCell))
5392 nCount = 1;
5394 break;
5395 case svRefList :
5396 case svDoubleRef :
5398 ScRange aRange;
5399 short nParam = 1;
5400 SCSIZE nRefListArrayPos = 0;
5401 size_t nRefInList = 0;
5402 while (nParam-- > 0)
5404 nRefListArrayPos = nRefInList;
5405 PopDoubleRef( aRange, nParam, nRefInList);
5406 nMaxCount +=
5407 static_cast<sal_uLong>(aRange.aEnd.Row() - aRange.aStart.Row() + 1) *
5408 static_cast<sal_uLong>(aRange.aEnd.Col() - aRange.aStart.Col() + 1) *
5409 static_cast<sal_uLong>(aRange.aEnd.Tab() - aRange.aStart.Tab() + 1);
5411 ScCellIterator aIter( mrDoc, aRange, mnSubTotalFlags);
5412 for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
5414 const ScRefCellValue& rCell = aIter.getRefCellValue();
5415 if (!isCellContentEmpty(rCell))
5416 ++nCount;
5418 if (xResMat)
5420 xResMat->PutDouble( nMaxCount - nCount, 0, nRefListArrayPos);
5421 nMaxCount = nCount = 0;
5425 break;
5426 case svMatrix:
5427 case svExternalSingleRef:
5428 case svExternalDoubleRef:
5430 ScMatrixRef xMat = GetMatrix();
5431 if (!xMat)
5432 SetError( FormulaError::IllegalParameter);
5433 else
5435 SCSIZE nC, nR;
5436 xMat->GetDimensions( nC, nR);
5437 nMaxCount = nC * nR;
5438 // Numbers (implicit), strings and error values, ignore empty
5439 // strings as those if not entered in an inline array are the
5440 // result of a formula, to be par with a reference to formula
5441 // cell as *visual* blank, see isCellContentEmpty() above.
5442 nCount = xMat->Count( true, true, true);
5445 break;
5446 default : SetError(FormulaError::IllegalParameter); break;
5448 if (xResMat)
5449 PushMatrix( xResMat);
5450 else
5451 PushDouble(nMaxCount - nCount);
5454 void ScInterpreter::IterateParametersIf( ScIterFuncIf eFunc )
5456 sal_uInt8 nParamCount = GetByte();
5457 if ( !MustHaveParamCount( nParamCount, 2, 3 ) )
5458 return;
5460 SCCOL nCol3 = 0;
5461 SCROW nRow3 = 0;
5462 SCTAB nTab3 = 0;
5464 ScMatrixRef pSumExtraMatrix;
5465 bool bSumExtraRange = (nParamCount == 3);
5466 if (bSumExtraRange)
5468 // Save only the upperleft cell in case of cell range. The geometry
5469 // of the 3rd parameter is taken from the 1st parameter.
5471 switch ( GetStackType() )
5473 case svDoubleRef :
5475 SCCOL nColJunk = 0;
5476 SCROW nRowJunk = 0;
5477 SCTAB nTabJunk = 0;
5478 PopDoubleRef( nCol3, nRow3, nTab3, nColJunk, nRowJunk, nTabJunk );
5479 if ( nTabJunk != nTab3 )
5481 PushError( FormulaError::IllegalParameter);
5482 return;
5485 break;
5486 case svSingleRef :
5487 PopSingleRef( nCol3, nRow3, nTab3 );
5488 break;
5489 case svMatrix:
5490 pSumExtraMatrix = PopMatrix();
5491 // nCol3, nRow3, nTab3 remain 0
5492 break;
5493 case svExternalSingleRef:
5495 pSumExtraMatrix = GetNewMat(1,1);
5496 ScExternalRefCache::TokenRef pToken;
5497 PopExternalSingleRef(pToken);
5498 if (nGlobalError != FormulaError::NONE)
5500 PushError( nGlobalError);
5501 return;
5504 if (pToken->GetType() == svDouble)
5505 pSumExtraMatrix->PutDouble(pToken->GetDouble(), 0, 0);
5506 else
5507 pSumExtraMatrix->PutString(pToken->GetString(), 0, 0);
5509 break;
5510 case svExternalDoubleRef:
5511 PopExternalDoubleRef(pSumExtraMatrix);
5512 break;
5513 default:
5514 PushError( FormulaError::IllegalParameter);
5515 return;
5519 svl::SharedString aString;
5520 double fVal = 0.0;
5521 bool bIsString = true;
5522 switch ( GetStackType() )
5524 case svDoubleRef :
5525 case svSingleRef :
5527 ScAddress aAdr;
5528 if ( !PopDoubleRefOrSingleRef( aAdr ) )
5530 PushError( nGlobalError);
5531 return;
5534 ScRefCellValue aCell(mrDoc, aAdr);
5535 switch (aCell.getType())
5537 case CELLTYPE_VALUE :
5538 fVal = GetCellValue(aAdr, aCell);
5539 bIsString = false;
5540 break;
5541 case CELLTYPE_FORMULA :
5542 if (aCell.getFormula()->IsValue())
5544 fVal = GetCellValue(aAdr, aCell);
5545 bIsString = false;
5547 else
5548 GetCellString(aString, aCell);
5549 break;
5550 case CELLTYPE_STRING :
5551 case CELLTYPE_EDIT :
5552 GetCellString(aString, aCell);
5553 break;
5554 default:
5555 fVal = 0.0;
5556 bIsString = false;
5559 break;
5560 case svString:
5561 aString = GetString();
5562 break;
5563 case svMatrix :
5564 case svExternalDoubleRef:
5566 ScMatValType nType = GetDoubleOrStringFromMatrix( fVal, aString);
5567 bIsString = ScMatrix::IsRealStringType( nType);
5569 break;
5570 case svExternalSingleRef:
5572 ScExternalRefCache::TokenRef pToken;
5573 PopExternalSingleRef(pToken);
5574 if (nGlobalError == FormulaError::NONE)
5576 if (pToken->GetType() == svDouble)
5578 fVal = pToken->GetDouble();
5579 bIsString = false;
5581 else
5582 aString = pToken->GetString();
5585 break;
5586 default:
5588 fVal = GetDouble();
5589 bIsString = false;
5593 KahanSum fSum = 0.0;
5594 double fRes = 0.0;
5595 double fCount = 0.0;
5596 short nParam = 1;
5597 const SCSIZE nMatRows = GetRefListArrayMaxSize( nParam);
5598 // There's either one RefList and nothing else, or none.
5599 ScMatrixRef xResMat = (nMatRows ? GetNewMat( 1, nMatRows, /*bEmpty*/true ) : nullptr);
5600 SCSIZE nRefListArrayPos = 0;
5601 size_t nRefInList = 0;
5602 while (nParam-- > 0)
5604 SCCOL nCol1 = 0;
5605 SCROW nRow1 = 0;
5606 SCTAB nTab1 = 0;
5607 SCCOL nCol2 = 0;
5608 SCROW nRow2 = 0;
5609 SCTAB nTab2 = 0;
5610 ScMatrixRef pQueryMatrix;
5611 switch ( GetStackType() )
5613 case svRefList :
5614 if (bSumExtraRange)
5616 /* TODO: this could resolve if all refs are of the same size */
5617 SetError( FormulaError::IllegalParameter);
5619 else
5621 nRefListArrayPos = nRefInList;
5622 ScRange aRange;
5623 PopDoubleRef( aRange, nParam, nRefInList);
5624 aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
5626 break;
5627 case svDoubleRef :
5628 PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
5629 break;
5630 case svSingleRef :
5631 PopSingleRef( nCol1, nRow1, nTab1 );
5632 nCol2 = nCol1;
5633 nRow2 = nRow1;
5634 nTab2 = nTab1;
5635 break;
5636 case svMatrix:
5637 case svExternalSingleRef:
5638 case svExternalDoubleRef:
5640 pQueryMatrix = GetMatrix();
5641 if (!pQueryMatrix)
5643 PushError( FormulaError::IllegalParameter);
5644 return;
5646 nCol1 = 0;
5647 nRow1 = 0;
5648 nTab1 = 0;
5649 SCSIZE nC, nR;
5650 pQueryMatrix->GetDimensions( nC, nR);
5651 nCol2 = static_cast<SCCOL>(nC - 1);
5652 nRow2 = static_cast<SCROW>(nR - 1);
5653 nTab2 = 0;
5655 break;
5656 default:
5657 SetError( FormulaError::IllegalParameter);
5659 if ( nTab1 != nTab2 )
5661 SetError( FormulaError::IllegalParameter);
5664 if (bSumExtraRange)
5666 // Take the range geometry of the 1st parameter and apply it to
5667 // the 3rd. If parts of the resulting range would point outside
5668 // the sheet, don't complain but silently ignore and simply cut
5669 // them away, this is what Xcl does :-/
5671 // For the cut-away part we also don't need to determine the
5672 // criteria match, so shrink the source range accordingly,
5673 // instead of the result range.
5674 SCCOL nColDelta = nCol2 - nCol1;
5675 SCROW nRowDelta = nRow2 - nRow1;
5676 SCCOL nMaxCol;
5677 SCROW nMaxRow;
5678 if (pSumExtraMatrix)
5680 SCSIZE nC, nR;
5681 pSumExtraMatrix->GetDimensions( nC, nR);
5682 nMaxCol = static_cast<SCCOL>(nC - 1);
5683 nMaxRow = static_cast<SCROW>(nR - 1);
5685 else
5687 nMaxCol = mrDoc.MaxCol();
5688 nMaxRow = mrDoc.MaxRow();
5690 if (nCol3 + nColDelta > nMaxCol)
5692 SCCOL nNewDelta = nMaxCol - nCol3;
5693 nCol2 = nCol1 + nNewDelta;
5696 if (nRow3 + nRowDelta > nMaxRow)
5698 SCROW nNewDelta = nMaxRow - nRow3;
5699 nRow2 = nRow1 + nNewDelta;
5702 else
5704 nCol3 = nCol1;
5705 nRow3 = nRow1;
5706 nTab3 = nTab1;
5709 if (nGlobalError == FormulaError::NONE)
5711 ScQueryParam rParam;
5712 rParam.nRow1 = nRow1;
5713 rParam.nRow2 = nRow2;
5715 ScQueryEntry& rEntry = rParam.GetEntry(0);
5716 ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
5717 rEntry.bDoQuery = true;
5718 if (!bIsString)
5720 rItem.meType = ScQueryEntry::ByValue;
5721 rItem.mfVal = fVal;
5722 rEntry.eOp = SC_EQUAL;
5724 else
5726 rParam.FillInExcelSyntax(mrDoc.GetSharedStringPool(), aString.getString(), 0, &mrContext);
5727 if (rItem.meType == ScQueryEntry::ByString)
5728 rParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc);
5730 ScAddress aAdr;
5731 aAdr.SetTab( nTab3 );
5732 rParam.nCol1 = nCol1;
5733 rParam.nCol2 = nCol2;
5734 rEntry.nField = nCol1;
5735 SCCOL nColDiff = nCol3 - nCol1;
5736 SCROW nRowDiff = nRow3 - nRow1;
5737 if (pQueryMatrix)
5739 // Never case-sensitive.
5740 sc::CompareOptions aOptions( mrDoc, rEntry, rParam.eSearchType);
5741 ScMatrixRef pResultMatrix = QueryMat( pQueryMatrix, aOptions);
5742 if (nGlobalError != FormulaError::NONE || !pResultMatrix)
5744 PushIllegalParameter();
5745 return;
5748 if (pSumExtraMatrix)
5750 for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
5752 for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
5754 if (pResultMatrix->IsValue( nCol, nRow) &&
5755 pResultMatrix->GetDouble( nCol, nRow))
5757 SCSIZE nC = nCol + nColDiff;
5758 SCSIZE nR = nRow + nRowDiff;
5759 if (pSumExtraMatrix->IsValue( nC, nR))
5761 fVal = pSumExtraMatrix->GetDouble( nC, nR);
5762 ++fCount;
5763 fSum += fVal;
5769 else if (!bSumExtraRange)
5771 for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
5773 for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
5775 if (pResultMatrix->IsValue( nCol, nRow) &&
5776 pResultMatrix->GetDouble( nCol, nRow))
5778 if (pQueryMatrix->IsValue( nCol, nRow))
5780 fVal = pQueryMatrix->GetDouble( nCol, nRow);
5781 ++fCount;
5782 fSum += fVal;
5788 else
5790 for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
5792 for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
5794 if (pResultMatrix->GetDouble( nCol, nRow))
5796 aAdr.SetCol( nCol + nColDiff);
5797 aAdr.SetRow( nRow + nRowDiff);
5798 ScRefCellValue aCell(mrDoc, aAdr);
5799 if (aCell.hasNumeric())
5801 fVal = GetCellValue(aAdr, aCell);
5802 ++fCount;
5803 fSum += fVal;
5810 else
5812 ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, rParam, false, false);
5813 // Increment Entry.nField in iterator when switching to next column.
5814 aCellIter.SetAdvanceQueryParamEntryField( true );
5815 if ( aCellIter.GetFirst() )
5817 if (pSumExtraMatrix)
5821 SCSIZE nC = aCellIter.GetCol() + nColDiff;
5822 SCSIZE nR = aCellIter.GetRow() + nRowDiff;
5823 if (pSumExtraMatrix->IsValue( nC, nR))
5825 fVal = pSumExtraMatrix->GetDouble( nC, nR);
5826 ++fCount;
5827 fSum += fVal;
5829 } while ( aCellIter.GetNext() );
5831 else
5835 aAdr.SetCol( aCellIter.GetCol() + nColDiff);
5836 aAdr.SetRow( aCellIter.GetRow() + nRowDiff);
5837 ScRefCellValue aCell(mrDoc, aAdr);
5838 if (aCell.hasNumeric())
5840 fVal = GetCellValue(aAdr, aCell);
5841 ++fCount;
5842 fSum += fVal;
5844 } while ( aCellIter.GetNext() );
5849 else
5851 PushError( FormulaError::IllegalParameter);
5852 return;
5855 switch( eFunc )
5857 case ifSUMIF: fRes = fSum.get(); break;
5858 case ifAVERAGEIF: fRes = div( fSum.get(), fCount ); break;
5860 if (xResMat)
5862 if (nGlobalError == FormulaError::NONE)
5863 xResMat->PutDouble( fRes, 0, nRefListArrayPos);
5864 else
5866 xResMat->PutError( nGlobalError, 0, nRefListArrayPos);
5867 nGlobalError = FormulaError::NONE;
5869 fRes = fCount = 0.0;
5870 fSum = 0;
5873 if (xResMat)
5874 PushMatrix( xResMat);
5875 else
5876 PushDouble( fRes);
5879 void ScInterpreter::ScSumIf()
5881 IterateParametersIf( ifSUMIF);
5884 void ScInterpreter::ScAverageIf()
5886 IterateParametersIf( ifAVERAGEIF);
5889 void ScInterpreter::ScCountIf()
5891 if ( !MustHaveParamCount( GetByte(), 2 ) )
5892 return;
5894 svl::SharedString aString;
5895 double fVal = 0.0;
5896 bool bIsString = true;
5897 switch ( GetStackType() )
5899 case svDoubleRef :
5900 case svSingleRef :
5902 ScAddress aAdr;
5903 if ( !PopDoubleRefOrSingleRef( aAdr ) )
5905 PushInt(0);
5906 return ;
5908 ScRefCellValue aCell(mrDoc, aAdr);
5909 switch (aCell.getType())
5911 case CELLTYPE_VALUE :
5912 fVal = GetCellValue(aAdr, aCell);
5913 bIsString = false;
5914 break;
5915 case CELLTYPE_FORMULA :
5916 if (aCell.getFormula()->IsValue())
5918 fVal = GetCellValue(aAdr, aCell);
5919 bIsString = false;
5921 else
5922 GetCellString(aString, aCell);
5923 break;
5924 case CELLTYPE_STRING :
5925 case CELLTYPE_EDIT :
5926 GetCellString(aString, aCell);
5927 break;
5928 default:
5929 fVal = 0.0;
5930 bIsString = false;
5933 break;
5934 case svMatrix:
5935 case svExternalSingleRef:
5936 case svExternalDoubleRef:
5938 ScMatValType nType = GetDoubleOrStringFromMatrix(fVal, aString);
5939 bIsString = ScMatrix::IsRealStringType( nType);
5941 break;
5942 case svString:
5943 aString = GetString();
5944 break;
5945 default:
5947 fVal = GetDouble();
5948 bIsString = false;
5951 double fCount = 0.0;
5952 short nParam = 1;
5953 const SCSIZE nMatRows = GetRefListArrayMaxSize( nParam);
5954 // There's either one RefList and nothing else, or none.
5955 ScMatrixRef xResMat = (nMatRows ? GetNewMat( 1, nMatRows, /*bEmpty*/true ) : nullptr);
5956 SCSIZE nRefListArrayPos = 0;
5957 size_t nRefInList = 0;
5958 while (nParam-- > 0)
5960 SCCOL nCol1 = 0;
5961 SCROW nRow1 = 0;
5962 SCTAB nTab1 = 0;
5963 SCCOL nCol2 = 0;
5964 SCROW nRow2 = 0;
5965 SCTAB nTab2 = 0;
5966 ScMatrixRef pQueryMatrix;
5967 const ScComplexRefData* refData = nullptr;
5968 switch ( GetStackType() )
5970 case svRefList :
5971 nRefListArrayPos = nRefInList;
5972 [[fallthrough]];
5973 case svDoubleRef :
5975 refData = GetStackDoubleRef(nRefInList);
5976 ScRange aRange;
5977 PopDoubleRef( aRange, nParam, nRefInList);
5978 aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
5980 break;
5981 case svSingleRef :
5982 PopSingleRef( nCol1, nRow1, nTab1 );
5983 nCol2 = nCol1;
5984 nRow2 = nRow1;
5985 nTab2 = nTab1;
5986 break;
5987 case svMatrix:
5988 case svExternalSingleRef:
5989 case svExternalDoubleRef:
5991 pQueryMatrix = GetMatrix();
5992 if (!pQueryMatrix)
5994 PushIllegalParameter();
5995 return;
5997 nCol1 = 0;
5998 nRow1 = 0;
5999 nTab1 = 0;
6000 SCSIZE nC, nR;
6001 pQueryMatrix->GetDimensions( nC, nR);
6002 nCol2 = static_cast<SCCOL>(nC - 1);
6003 nRow2 = static_cast<SCROW>(nR - 1);
6004 nTab2 = 0;
6006 break;
6007 default:
6008 PopError(); // Propagate it further
6009 PushIllegalParameter();
6010 return ;
6012 if ( nTab1 != nTab2 )
6014 PushIllegalParameter();
6015 return;
6017 if (nCol1 > nCol2)
6019 PushIllegalParameter();
6020 return;
6022 if (nGlobalError == FormulaError::NONE)
6024 ScQueryParam rParam;
6025 rParam.nRow1 = nRow1;
6026 rParam.nRow2 = nRow2;
6027 rParam.nTab = nTab1;
6029 ScQueryEntry& rEntry = rParam.GetEntry(0);
6030 ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
6031 rEntry.bDoQuery = true;
6032 if (!bIsString)
6034 rItem.meType = ScQueryEntry::ByValue;
6035 rItem.mfVal = fVal;
6036 rEntry.eOp = SC_EQUAL;
6038 else
6040 rParam.FillInExcelSyntax(mrDoc.GetSharedStringPool(), aString.getString(), 0, &mrContext);
6041 if (rItem.meType == ScQueryEntry::ByString)
6042 rParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc);
6044 rParam.nCol1 = nCol1;
6045 rParam.nCol2 = nCol2;
6046 rEntry.nField = nCol1;
6047 if (pQueryMatrix)
6049 // Never case-sensitive.
6050 sc::CompareOptions aOptions( mrDoc, rEntry, rParam.eSearchType);
6051 ScMatrixRef pResultMatrix = QueryMat( pQueryMatrix, aOptions);
6052 if (nGlobalError != FormulaError::NONE || !pResultMatrix)
6054 PushIllegalParameter();
6055 return;
6058 SCSIZE nSize = pResultMatrix->GetElementCount();
6059 for (SCSIZE nIndex = 0; nIndex < nSize; ++nIndex)
6061 if (pResultMatrix->IsValue( nIndex) &&
6062 pResultMatrix->GetDouble( nIndex))
6063 ++fCount;
6066 else
6068 if(ScCountIfCellIteratorSortedCache::CanBeUsed(mrDoc, rParam, nTab1, pMyFormulaCell,
6069 refData, mrContext))
6071 ScCountIfCellIteratorSortedCache aCellIter(mrDoc, mrContext, nTab1, rParam, false, false);
6072 fCount += aCellIter.GetCount();
6074 else
6076 ScCountIfCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, rParam, false, false);
6077 fCount += aCellIter.GetCount();
6081 else
6083 PushIllegalParameter();
6084 return;
6086 if (xResMat)
6088 xResMat->PutDouble( fCount, 0, nRefListArrayPos);
6089 fCount = 0.0;
6092 if (xResMat)
6093 PushMatrix( xResMat);
6094 else
6095 PushDouble(fCount);
6098 void ScInterpreter::IterateParametersIfs( double(*ResultFunc)( const sc::ParamIfsResult& rRes ) )
6100 sal_uInt8 nParamCount = GetByte();
6101 sal_uInt8 nQueryCount = nParamCount / 2;
6103 std::vector<sal_uInt8>& vConditions = mrContext.maConditions;
6104 // vConditions is cached, although it is clear'ed after every cell is interpreted,
6105 // if the SUMIFS/COUNTIFS are part of a matrix formula, then that is not enough because
6106 // with a single InterpretTail() call it results in evaluation of all the cells in the
6107 // matrix formula.
6108 vConditions.clear();
6110 // Range-reduce optimization
6111 SCCOL nStartColDiff = 0;
6112 SCCOL nEndColDiff = 0;
6113 SCROW nStartRowDiff = 0;
6114 SCROW nEndRowDiff = 0;
6115 bool bRangeReduce = false;
6116 ScRange aMainRange;
6118 bool bHasDoubleRefCriteriaRanges = true;
6119 // Do not attempt main-range reduce if any of the criteria-ranges are not double-refs.
6120 // For COUNTIFS queries it's possible to range-reduce too, if the query is not supposed
6121 // to match empty cells (will be checked and undone later if needed), so simply treat
6122 // the first criteria range as the main range for purposes of detecting if this can be done.
6123 for (sal_uInt16 nParamIdx = 2; nParamIdx < nParamCount; nParamIdx += 2 )
6125 const formula::FormulaToken* pCriteriaRangeToken = pStack[ sp-nParamIdx ];
6126 if (pCriteriaRangeToken->GetType() != svDoubleRef )
6128 bHasDoubleRefCriteriaRanges = false;
6129 break;
6133 // Probe the main range token, and try if we can shrink the range without altering results.
6134 const formula::FormulaToken* pMainRangeToken = pStack[ sp-nParamCount ];
6135 if (pMainRangeToken->GetType() == svDoubleRef && bHasDoubleRefCriteriaRanges)
6137 const ScComplexRefData* pRefData = pMainRangeToken->GetDoubleRef();
6138 if (!pRefData->IsDeleted())
6140 DoubleRefToRange( *pRefData, aMainRange);
6141 if (aMainRange.aStart.Tab() == aMainRange.aEnd.Tab())
6143 // Shrink the range to actual data content.
6144 ScRange aSubRange = aMainRange;
6145 mrDoc.GetDataAreaSubrange(aSubRange);
6146 nStartColDiff = aSubRange.aStart.Col() - aMainRange.aStart.Col();
6147 nStartRowDiff = aSubRange.aStart.Row() - aMainRange.aStart.Row();
6148 nEndColDiff = aSubRange.aEnd.Col() - aMainRange.aEnd.Col();
6149 nEndRowDiff = aSubRange.aEnd.Row() - aMainRange.aEnd.Row();
6150 bRangeReduce = nStartColDiff || nStartRowDiff || nEndColDiff || nEndRowDiff;
6155 double fVal = 0.0;
6156 SCCOL nDimensionCols = 0;
6157 SCROW nDimensionRows = 0;
6158 const SCSIZE nRefArrayRows = GetRefListArrayMaxSize( nParamCount);
6159 std::vector<std::vector<sal_uInt8>> vRefArrayConditions;
6161 while (nParamCount > 1 && nGlobalError == FormulaError::NONE)
6163 // take criteria
6164 svl::SharedString aString;
6165 fVal = 0.0;
6166 bool bIsString = true;
6167 switch ( GetStackType() )
6169 case svDoubleRef :
6170 case svSingleRef :
6172 ScAddress aAdr;
6173 if ( !PopDoubleRefOrSingleRef( aAdr ) )
6175 PushError( nGlobalError);
6176 return;
6179 ScRefCellValue aCell(mrDoc, aAdr);
6180 switch (aCell.getType())
6182 case CELLTYPE_VALUE :
6183 fVal = GetCellValue(aAdr, aCell);
6184 bIsString = false;
6185 break;
6186 case CELLTYPE_FORMULA :
6187 if (aCell.getFormula()->IsValue())
6189 fVal = GetCellValue(aAdr, aCell);
6190 bIsString = false;
6192 else
6193 GetCellString(aString, aCell);
6194 break;
6195 case CELLTYPE_STRING :
6196 case CELLTYPE_EDIT :
6197 GetCellString(aString, aCell);
6198 break;
6199 default:
6200 fVal = 0.0;
6201 bIsString = false;
6204 break;
6205 case svString:
6206 aString = GetString();
6207 break;
6208 case svMatrix :
6209 case svExternalDoubleRef:
6211 ScMatValType nType = GetDoubleOrStringFromMatrix( fVal, aString);
6212 bIsString = ScMatrix::IsRealStringType( nType);
6214 break;
6215 case svExternalSingleRef:
6217 ScExternalRefCache::TokenRef pToken;
6218 PopExternalSingleRef(pToken);
6219 if (nGlobalError == FormulaError::NONE)
6221 if (pToken->GetType() == svDouble)
6223 fVal = pToken->GetDouble();
6224 bIsString = false;
6226 else
6227 aString = pToken->GetString();
6230 break;
6231 default:
6233 fVal = GetDouble();
6234 bIsString = false;
6238 if (nGlobalError != FormulaError::NONE)
6240 PushError( nGlobalError);
6241 return; // and bail out, no need to evaluate other arguments
6244 // take range
6245 short nParam = nParamCount;
6246 size_t nRefInList = 0;
6247 size_t nRefArrayPos = std::numeric_limits<size_t>::max();
6248 SCCOL nCol1 = 0;
6249 SCROW nRow1 = 0;
6250 SCTAB nTab1 = 0;
6251 SCCOL nCol2 = 0;
6252 SCROW nRow2 = 0;
6253 SCTAB nTab2 = 0;
6254 ScMatrixRef pQueryMatrix;
6255 while (nParam-- == nParamCount)
6257 const ScComplexRefData* refData = nullptr;
6258 switch ( GetStackType() )
6260 case svRefList :
6262 const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]);
6263 if (p && p->IsArrayResult())
6265 if (nRefInList == 0)
6267 if (vRefArrayConditions.empty())
6268 vRefArrayConditions.resize( nRefArrayRows);
6269 if (!vConditions.empty())
6271 // Similar to other reference list array
6272 // handling, add/op the current value to
6273 // all array positions.
6274 for (auto & rVec : vRefArrayConditions)
6276 if (rVec.empty())
6277 rVec = vConditions;
6278 else
6280 assert(rVec.size() == vConditions.size()); // see dimensions below
6281 for (size_t i=0, n = rVec.size(); i < n; ++i)
6283 rVec[i] += vConditions[i];
6287 // Reset condition results.
6288 std::for_each( vConditions.begin(), vConditions.end(),
6289 [](sal_uInt8 & r){ r = 0.0; } );
6292 nRefArrayPos = nRefInList;
6294 refData = GetStackDoubleRef(nRefInList);
6295 ScRange aRange;
6296 PopDoubleRef( aRange, nParam, nRefInList);
6297 aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
6299 break;
6300 case svDoubleRef :
6301 refData = GetStackDoubleRef();
6302 PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
6303 break;
6304 case svSingleRef :
6305 PopSingleRef( nCol1, nRow1, nTab1 );
6306 nCol2 = nCol1;
6307 nRow2 = nRow1;
6308 nTab2 = nTab1;
6309 break;
6310 case svMatrix:
6311 case svExternalSingleRef:
6312 case svExternalDoubleRef:
6314 pQueryMatrix = GetMatrix();
6315 if (!pQueryMatrix)
6317 PushError( FormulaError::IllegalParameter);
6318 return;
6320 nCol1 = 0;
6321 nRow1 = 0;
6322 nTab1 = 0;
6323 SCSIZE nC, nR;
6324 pQueryMatrix->GetDimensions( nC, nR);
6325 nCol2 = static_cast<SCCOL>(nC - 1);
6326 nRow2 = static_cast<SCROW>(nR - 1);
6327 nTab2 = 0;
6329 break;
6330 default:
6331 PushError( FormulaError::IllegalParameter);
6332 return;
6334 if ( nTab1 != nTab2 )
6336 PushError( FormulaError::IllegalArgument);
6337 return;
6340 ScQueryParam rParam;
6341 ScQueryEntry& rEntry = rParam.GetEntry(0);
6342 ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
6343 rEntry.bDoQuery = true;
6344 if (!bIsString)
6346 rItem.meType = ScQueryEntry::ByValue;
6347 rItem.mfVal = fVal;
6348 rEntry.eOp = SC_EQUAL;
6350 else
6352 rParam.FillInExcelSyntax(mrDoc.GetSharedStringPool(), aString.getString(), 0, &mrContext);
6353 if (rItem.meType == ScQueryEntry::ByString)
6354 rParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc);
6357 // Undo bRangeReduce if asked to match empty cells for COUNTIFS (which should be rare).
6358 assert(rEntry.GetQueryItems().size() == 1);
6359 const bool isCountIfs = (nParamCount % 2) == 0;
6360 if(isCountIfs && (rEntry.IsQueryByEmpty() || rItem.mbMatchEmpty) && bRangeReduce)
6362 bRangeReduce = false;
6363 // All criteria ranges are svDoubleRef's, so only vConditions needs adjusting.
6364 assert(vRefArrayConditions.empty());
6365 if(!vConditions.empty())
6367 std::vector<sal_uInt8> newConditions;
6368 SCCOL newDimensionCols = nCol2 - nCol1 + 1;
6369 SCROW newDimensionRows = nRow2 - nRow1 + 1;
6370 newConditions.reserve( newDimensionCols * newDimensionRows );
6371 SCCOL col = nCol1;
6372 for(; col < nCol1 + nStartColDiff; ++col)
6373 newConditions.insert( newConditions.end(), newDimensionRows, 0 );
6374 for(; col <= nCol2 - nStartColDiff; ++col)
6376 newConditions.insert( newConditions.end(), nStartRowDiff, 0 );
6377 SCCOL oldCol = col - ( nCol1 + nStartColDiff );
6378 size_t nIndex = oldCol * nDimensionRows;
6379 if (nIndex < vConditions.size())
6381 auto it = vConditions.begin() + nIndex;
6382 newConditions.insert( newConditions.end(), it, it + nDimensionRows );
6384 else
6385 newConditions.insert( newConditions.end(), nDimensionRows, 0 );
6386 newConditions.insert( newConditions.end(), -nEndRowDiff, 0 );
6388 for(; col <= nCol2; ++col)
6389 newConditions.insert( newConditions.end(), newDimensionRows, 0 );
6390 assert( newConditions.size() == o3tl::make_unsigned( newDimensionCols * newDimensionRows ));
6391 vConditions = std::move( newConditions );
6392 nDimensionCols = newDimensionCols;
6393 nDimensionRows = newDimensionRows;
6397 if (bRangeReduce)
6399 // All reference ranges must be of the same size as the main range.
6400 if( aMainRange.aEnd.Col() - aMainRange.aStart.Col() != nCol2 - nCol1
6401 || aMainRange.aEnd.Row() - aMainRange.aStart.Row() != nRow2 - nRow1)
6403 PushError ( FormulaError::IllegalArgument);
6404 return;
6406 nCol1 += nStartColDiff;
6407 nRow1 += nStartRowDiff;
6409 nCol2 += nEndColDiff;
6410 nRow2 += nEndRowDiff;
6413 // All reference ranges must be of same dimension and size.
6414 if (!nDimensionCols)
6415 nDimensionCols = nCol2 - nCol1 + 1;
6416 if (!nDimensionRows)
6417 nDimensionRows = nRow2 - nRow1 + 1;
6418 if ((nDimensionCols != (nCol2 - nCol1 + 1)) || (nDimensionRows != (nRow2 - nRow1 + 1)))
6420 PushError ( FormulaError::IllegalArgument);
6421 return;
6424 // recalculate matrix values
6425 if (nGlobalError != FormulaError::NONE)
6427 PushError( nGlobalError);
6428 return;
6431 // initialize temporary result matrix
6432 if (vConditions.empty())
6433 vConditions.resize( nDimensionCols * nDimensionRows, 0);
6435 rParam.nRow1 = nRow1;
6436 rParam.nRow2 = nRow2;
6437 rParam.nCol1 = nCol1;
6438 rParam.nCol2 = nCol2;
6439 rEntry.nField = nCol1;
6440 SCCOL nColDiff = -nCol1;
6441 SCROW nRowDiff = -nRow1;
6442 if (pQueryMatrix)
6444 // Never case-sensitive.
6445 sc::CompareOptions aOptions(mrDoc, rEntry, rParam.eSearchType);
6446 ScMatrixRef pResultMatrix = QueryMat( pQueryMatrix, aOptions);
6447 if (nGlobalError != FormulaError::NONE || !pResultMatrix)
6449 PushError( FormulaError::IllegalParameter);
6450 return;
6453 // result matrix is filled with boolean values.
6454 std::vector<double> aResValues;
6455 pResultMatrix->GetDoubleArray(aResValues);
6456 if (vConditions.size() != aResValues.size())
6458 PushError( FormulaError::IllegalParameter);
6459 return;
6462 std::vector<double>::const_iterator itThisRes = aResValues.begin();
6463 for (auto& rCondition : vConditions)
6465 rCondition += *itThisRes;
6466 ++itThisRes;
6469 else
6471 if( ScQueryCellIteratorSortedCache::CanBeUsed( mrDoc, rParam, nTab1, pMyFormulaCell,
6472 refData, mrContext ))
6474 ScQueryCellIteratorSortedCache aCellIter(mrDoc, mrContext, nTab1, rParam, false, false);
6475 // Increment Entry.nField in iterator when switching to next column.
6476 aCellIter.SetAdvanceQueryParamEntryField( true );
6477 if ( aCellIter.GetFirst() )
6481 size_t nC = aCellIter.GetCol() + nColDiff;
6482 size_t nR = aCellIter.GetRow() + nRowDiff;
6483 ++vConditions[nC * nDimensionRows + nR];
6484 } while ( aCellIter.GetNext() );
6487 else
6489 ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, rParam, false, false);
6490 // Increment Entry.nField in iterator when switching to next column.
6491 aCellIter.SetAdvanceQueryParamEntryField( true );
6492 if ( aCellIter.GetFirst() )
6496 size_t nC = aCellIter.GetCol() + nColDiff;
6497 size_t nR = aCellIter.GetRow() + nRowDiff;
6498 ++vConditions[nC * nDimensionRows + nR];
6499 } while ( aCellIter.GetNext() );
6503 if (nRefArrayPos != std::numeric_limits<size_t>::max())
6505 // Apply condition result to reference list array result position.
6506 std::vector<sal_uInt8>& rVec = vRefArrayConditions[nRefArrayPos];
6507 if (rVec.empty())
6508 rVec = vConditions;
6509 else
6511 assert(rVec.size() == vConditions.size()); // see dimensions above
6512 for (size_t i=0, n = rVec.size(); i < n; ++i)
6514 rVec[i] += vConditions[i];
6517 // Reset conditions vector.
6518 // When leaving an svRefList this has to be emptied not set to
6519 // 0.0 because it's checked when entering an svRefList.
6520 if (nRefInList == 0)
6521 std::vector<sal_uInt8>().swap( vConditions);
6522 else
6523 std::for_each( vConditions.begin(), vConditions.end(), [](sal_uInt8 & r){ r = 0; } );
6526 nParamCount -= 2;
6529 if (!vRefArrayConditions.empty() && !vConditions.empty())
6531 // Add/op the last current value to all array positions.
6532 for (auto & rVec : vRefArrayConditions)
6534 if (rVec.empty())
6535 rVec = vConditions;
6536 else
6538 assert(rVec.size() == vConditions.size()); // see dimensions above
6539 for (size_t i=0, n = rVec.size(); i < n; ++i)
6541 rVec[i] += vConditions[i];
6547 if (nGlobalError != FormulaError::NONE)
6549 PushError( nGlobalError);
6550 return; // bail out
6553 sc::ParamIfsResult aRes;
6554 ScMatrixRef xResMat;
6556 // main range - only for AVERAGEIFS, SUMIFS, MINIFS and MAXIFS
6557 if (nParamCount == 1)
6559 short nParam = nParamCount;
6560 size_t nRefInList = 0;
6561 size_t nRefArrayPos = std::numeric_limits<size_t>::max();
6562 bool bRefArrayMain = false;
6563 while (nParam-- == nParamCount)
6565 SCCOL nMainCol1 = 0;
6566 SCROW nMainRow1 = 0;
6567 SCTAB nMainTab1 = 0;
6568 SCCOL nMainCol2 = 0;
6569 SCROW nMainRow2 = 0;
6570 SCTAB nMainTab2 = 0;
6571 ScMatrixRef pMainMatrix;
6572 switch ( GetStackType() )
6574 case svRefList :
6576 const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]);
6577 if (p && p->IsArrayResult())
6579 if (vRefArrayConditions.empty())
6581 // Replicate conditions if there wasn't a
6582 // reference list array for criteria
6583 // evaluation.
6584 vRefArrayConditions.resize( nRefArrayRows);
6585 for (auto & rVec : vRefArrayConditions)
6587 rVec = vConditions;
6591 bRefArrayMain = true;
6592 nRefArrayPos = nRefInList;
6594 ScRange aRange;
6595 PopDoubleRef( aRange, nParam, nRefInList);
6596 aRange.GetVars( nMainCol1, nMainRow1, nMainTab1, nMainCol2, nMainRow2, nMainTab2);
6598 break;
6599 case svDoubleRef :
6600 PopDoubleRef( nMainCol1, nMainRow1, nMainTab1, nMainCol2, nMainRow2, nMainTab2 );
6601 break;
6602 case svSingleRef :
6603 PopSingleRef( nMainCol1, nMainRow1, nMainTab1 );
6604 nMainCol2 = nMainCol1;
6605 nMainRow2 = nMainRow1;
6606 nMainTab2 = nMainTab1;
6607 break;
6608 case svMatrix:
6609 case svExternalSingleRef:
6610 case svExternalDoubleRef:
6612 pMainMatrix = GetMatrix();
6613 if (!pMainMatrix)
6615 PushError( FormulaError::IllegalParameter);
6616 return;
6618 nMainCol1 = 0;
6619 nMainRow1 = 0;
6620 nMainTab1 = 0;
6621 SCSIZE nC, nR;
6622 pMainMatrix->GetDimensions( nC, nR);
6623 nMainCol2 = static_cast<SCCOL>(nC - 1);
6624 nMainRow2 = static_cast<SCROW>(nR - 1);
6625 nMainTab2 = 0;
6627 break;
6628 // Treat a scalar value as 1x1 matrix.
6629 case svDouble:
6630 pMainMatrix = GetNewMat(1,1);
6631 nMainCol1 = nMainCol2 = 0;
6632 nMainRow1 = nMainRow2 = 0;
6633 nMainTab1 = nMainTab2 = 0;
6634 pMainMatrix->PutDouble( GetDouble(), 0, 0);
6635 break;
6636 case svString:
6637 pMainMatrix = GetNewMat(1,1);
6638 nMainCol1 = nMainCol2 = 0;
6639 nMainRow1 = nMainRow2 = 0;
6640 nMainTab1 = nMainTab2 = 0;
6641 pMainMatrix->PutString( GetString(), 0, 0);
6642 break;
6643 default:
6644 PopError();
6645 PushError( FormulaError::IllegalParameter);
6646 return;
6648 if ( nMainTab1 != nMainTab2 )
6650 PushError( FormulaError::IllegalArgument);
6651 return;
6654 if (bRangeReduce)
6656 nMainCol1 += nStartColDiff;
6657 nMainRow1 += nStartRowDiff;
6659 nMainCol2 += nEndColDiff;
6660 nMainRow2 += nEndRowDiff;
6663 // All reference ranges must be of same dimension and size.
6664 if ((nDimensionCols != (nMainCol2 - nMainCol1 + 1)) || (nDimensionRows != (nMainRow2 - nMainRow1 + 1)))
6666 PushError ( FormulaError::IllegalArgument);
6667 return;
6670 if (nGlobalError != FormulaError::NONE)
6672 PushError( nGlobalError);
6673 return; // bail out
6676 // end-result calculation
6678 // This gets weird... if conditions were calculated using a
6679 // reference list array but the main calculation range is not a
6680 // reference list array, then the conditions of the array are
6681 // applied to the main range each in turn to form the array result.
6683 size_t nRefArrayMainPos = (bRefArrayMain ? nRefArrayPos :
6684 (vRefArrayConditions.empty() ? std::numeric_limits<size_t>::max() : 0));
6685 const bool bAppliedArray = (!bRefArrayMain && nRefArrayMainPos == 0);
6687 if (nRefArrayMainPos == 0)
6688 xResMat = GetNewMat( 1, nRefArrayRows, /*bEmpty*/true );
6690 if (pMainMatrix)
6692 std::vector<double> aMainValues;
6693 pMainMatrix->GetDoubleArray(aMainValues, false); // Map empty values to NaN's.
6697 if (nRefArrayMainPos < vRefArrayConditions.size())
6698 vConditions = vRefArrayConditions[nRefArrayMainPos];
6700 if (vConditions.size() != aMainValues.size())
6702 PushError( FormulaError::IllegalArgument);
6703 return;
6706 std::vector<sal_uInt8>::const_iterator itRes = vConditions.begin(), itResEnd = vConditions.end();
6707 std::vector<double>::const_iterator itMain = aMainValues.begin();
6708 for (; itRes != itResEnd; ++itRes, ++itMain)
6710 if (*itRes != nQueryCount)
6711 continue;
6713 fVal = *itMain;
6714 if (GetDoubleErrorValue(fVal) == FormulaError::ElementNaN)
6715 continue;
6717 ++aRes.mfCount;
6718 aRes.mfSum += fVal;
6719 if ( aRes.mfMin > fVal )
6720 aRes.mfMin = fVal;
6721 if ( aRes.mfMax < fVal )
6722 aRes.mfMax = fVal;
6724 if (nRefArrayMainPos != std::numeric_limits<size_t>::max())
6726 xResMat->PutDouble( ResultFunc( aRes), 0, nRefArrayMainPos);
6727 aRes = sc::ParamIfsResult();
6730 while (bAppliedArray && ++nRefArrayMainPos < nRefArrayRows);
6732 else
6734 ScAddress aAdr;
6735 aAdr.SetTab( nMainTab1 );
6738 if (nRefArrayMainPos < vRefArrayConditions.size())
6739 vConditions = vRefArrayConditions[nRefArrayMainPos];
6741 SAL_WARN_IF(nDimensionCols && nDimensionRows && vConditions.empty(), "sc", "ScInterpreter::IterateParametersIfs vConditions is empty");
6742 if (!vConditions.empty())
6744 std::vector<sal_uInt8>::const_iterator itRes = vConditions.begin();
6745 for (SCCOL nCol = 0; nCol < nDimensionCols; ++nCol)
6747 for (SCROW nRow = 0; nRow < nDimensionRows; ++nRow, ++itRes)
6749 if (*itRes == nQueryCount)
6751 aAdr.SetCol( nCol + nMainCol1);
6752 aAdr.SetRow( nRow + nMainRow1);
6753 ScRefCellValue aCell(mrDoc, aAdr);
6754 if (aCell.hasNumeric())
6756 fVal = GetCellValue(aAdr, aCell);
6757 ++aRes.mfCount;
6758 aRes.mfSum += fVal;
6759 if ( aRes.mfMin > fVal )
6760 aRes.mfMin = fVal;
6761 if ( aRes.mfMax < fVal )
6762 aRes.mfMax = fVal;
6768 if (nRefArrayMainPos != std::numeric_limits<size_t>::max())
6770 xResMat->PutDouble( ResultFunc( aRes), 0, nRefArrayMainPos);
6771 aRes = sc::ParamIfsResult();
6774 while (bAppliedArray && ++nRefArrayMainPos < nRefArrayRows);
6778 else
6780 // COUNTIFS only.
6781 if (vRefArrayConditions.empty())
6783 // The code below is this but optimized for most elements not matching.
6784 // for (auto const & rCond : vConditions)
6785 // if (rCond == nQueryCount)
6786 // ++aRes.mfCount;
6787 static_assert(sizeof(vConditions[0]) == 1);
6788 const sal_uInt8* pos = vConditions.data();
6789 const sal_uInt8* end = pos + vConditions.size();
6790 for(;;)
6792 pos = static_cast< const sal_uInt8* >( memchr( pos, nQueryCount, end - pos ));
6793 if( pos == nullptr )
6794 break;
6795 ++aRes.mfCount;
6796 ++pos;
6799 else
6801 xResMat = GetNewMat( 1, nRefArrayRows, /*bEmpty*/true );
6802 for (size_t i=0, n = vRefArrayConditions.size(); i < n; ++i)
6804 double fCount = 0.0;
6805 for (auto const & rCond : vRefArrayConditions[i])
6807 if (rCond == nQueryCount)
6808 ++fCount;
6810 xResMat->PutDouble( fCount, 0, i);
6815 if (xResMat)
6816 PushMatrix( xResMat);
6817 else
6818 PushDouble( ResultFunc( aRes));
6821 void ScInterpreter::ScSumIfs()
6823 // ScMutationGuard aShouldFail(pDok, ScMutationGuardFlags::CORE);
6824 sal_uInt8 nParamCount = GetByte();
6826 if (nParamCount < 3 || (nParamCount % 2 != 1))
6828 PushError( FormulaError::ParameterExpected);
6829 return;
6832 auto ResultFunc = []( const sc::ParamIfsResult& rRes )
6834 return rRes.mfSum.get();
6836 IterateParametersIfs(ResultFunc);
6839 void ScInterpreter::ScAverageIfs()
6841 sal_uInt8 nParamCount = GetByte();
6843 if (nParamCount < 3 || (nParamCount % 2 != 1))
6845 PushError( FormulaError::ParameterExpected);
6846 return;
6849 auto ResultFunc = []( const sc::ParamIfsResult& rRes )
6851 return sc::div( rRes.mfSum.get(), rRes.mfCount);
6853 IterateParametersIfs(ResultFunc);
6856 void ScInterpreter::ScCountIfs()
6858 sal_uInt8 nParamCount = GetByte();
6860 if (nParamCount < 2 || (nParamCount % 2 != 0))
6862 PushError( FormulaError::ParameterExpected);
6863 return;
6866 auto ResultFunc = []( const sc::ParamIfsResult& rRes )
6868 return rRes.mfCount;
6870 IterateParametersIfs(ResultFunc);
6873 void ScInterpreter::ScMinIfs_MS()
6875 sal_uInt8 nParamCount = GetByte();
6877 if (nParamCount < 3 || (nParamCount % 2 != 1))
6879 PushError( FormulaError::ParameterExpected);
6880 return;
6883 auto ResultFunc = []( const sc::ParamIfsResult& rRes )
6885 return (rRes.mfMin < std::numeric_limits<double>::max()) ? rRes.mfMin : 0.0;
6887 IterateParametersIfs(ResultFunc);
6891 void ScInterpreter::ScMaxIfs_MS()
6893 sal_uInt8 nParamCount = GetByte();
6895 if (nParamCount < 3 || (nParamCount % 2 != 1))
6897 PushError( FormulaError::ParameterExpected);
6898 return;
6901 auto ResultFunc = []( const sc::ParamIfsResult& rRes )
6903 return (rRes.mfMax > std::numeric_limits<double>::lowest()) ? rRes.mfMax : 0.0;
6905 IterateParametersIfs(ResultFunc);
6908 void ScInterpreter::ScLookup()
6910 sal_uInt8 nParamCount = GetByte();
6911 if ( !MustHaveParamCount( nParamCount, 2, 3 ) )
6912 return ;
6914 ScMatrixRef pDataMat = nullptr, pResMat = nullptr;
6915 SCCOL nCol1 = 0, nCol2 = 0, nResCol1 = 0, nResCol2 = 0;
6916 SCROW nRow1 = 0, nRow2 = 0, nResRow1 = 0, nResRow2 = 0;
6917 SCTAB nTab1 = 0, nResTab = 0;
6918 SCSIZE nLenMajor = 0; // length of major direction
6919 bool bVertical = true; // whether to lookup vertically or horizontally
6921 // The third parameter, result array, double, string and reference.
6922 double fResVal = 0.0;
6923 svl::SharedString aResStr;
6924 StackVar eResArrayType = svUnknown;
6926 if (nParamCount == 3)
6928 eResArrayType = GetStackType();
6929 switch (eResArrayType)
6931 case svDoubleRef:
6933 SCTAB nTabJunk;
6934 PopDoubleRef(nResCol1, nResRow1, nResTab,
6935 nResCol2, nResRow2, nTabJunk);
6936 if (nResTab != nTabJunk ||
6937 ((nResRow2 - nResRow1) > 0 && (nResCol2 - nResCol1) > 0))
6939 // The result array must be a vector.
6940 PushIllegalParameter();
6941 return;
6944 break;
6945 case svSingleRef:
6946 PopSingleRef( nResCol1, nResRow1, nResTab);
6947 nResCol2 = nResCol1;
6948 nResRow2 = nResRow1;
6949 break;
6950 case svMatrix:
6951 case svExternalSingleRef:
6952 case svExternalDoubleRef:
6954 pResMat = GetMatrix();
6955 if (!pResMat)
6957 PushIllegalParameter();
6958 return;
6960 SCSIZE nC, nR;
6961 pResMat->GetDimensions(nC, nR);
6962 if (nC != 1 && nR != 1)
6964 // Result matrix must be a vector.
6965 PushIllegalParameter();
6966 return;
6969 break;
6970 case svDouble:
6971 fResVal = GetDouble();
6972 break;
6973 case svString:
6974 aResStr = GetString();
6975 break;
6976 default:
6977 PushIllegalParameter();
6978 return;
6982 // For double, string and single reference.
6983 double fDataVal = 0.0;
6984 svl::SharedString aDataStr;
6985 ScAddress aDataAdr;
6986 bool bValueData = false;
6988 // Get the data-result range and also determine whether this is vertical
6989 // lookup or horizontal lookup.
6991 StackVar eDataArrayType = GetStackType();
6992 switch (eDataArrayType)
6994 case svDoubleRef:
6996 SCTAB nTabJunk;
6997 PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTabJunk);
6998 if (nTab1 != nTabJunk)
7000 PushIllegalParameter();
7001 return;
7003 bVertical = (nRow2 - nRow1) >= (nCol2 - nCol1);
7004 nLenMajor = bVertical ? nRow2 - nRow1 + 1 : nCol2 - nCol1 + 1;
7006 break;
7007 case svMatrix:
7008 case svExternalSingleRef:
7009 case svExternalDoubleRef:
7011 pDataMat = GetMatrix();
7012 if (!pDataMat)
7014 PushIllegalParameter();
7015 return;
7018 SCSIZE nC, nR;
7019 pDataMat->GetDimensions(nC, nR);
7020 bVertical = (nR >= nC);
7021 nLenMajor = bVertical ? nR : nC;
7023 break;
7024 case svDouble:
7026 fDataVal = GetDouble();
7027 bValueData = true;
7029 break;
7030 case svString:
7032 aDataStr = GetString();
7034 break;
7035 case svSingleRef:
7037 PopSingleRef( aDataAdr );
7038 ScRefCellValue aCell(mrDoc, aDataAdr);
7039 if (aCell.hasEmptyValue())
7041 // Empty cells aren't found anywhere, bail out early.
7042 SetError( FormulaError::NotAvailable);
7044 else if (aCell.hasNumeric())
7046 fDataVal = GetCellValue(aDataAdr, aCell);
7047 bValueData = true;
7049 else
7050 GetCellString(aDataStr, aCell);
7052 break;
7053 default:
7054 SetError( FormulaError::IllegalParameter);
7057 if (nGlobalError != FormulaError::NONE)
7059 PushError( nGlobalError);
7060 return;
7063 // Get the lookup value.
7065 ScQueryParam aParam;
7066 ScQueryEntry& rEntry = aParam.GetEntry(0);
7067 if ( !FillEntry(rEntry) )
7068 return;
7070 if ( eDataArrayType == svDouble || eDataArrayType == svString ||
7071 eDataArrayType == svSingleRef )
7073 // Delta position for a single value is always 0.
7075 // Found if data <= query, but not if query is string and found data is
7076 // numeric or vice versa. This is how Excel does it but doesn't
7077 // document it.
7079 bool bFound = false;
7080 ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
7082 if ( bValueData )
7084 if (rItem.meType == ScQueryEntry::ByString)
7085 bFound = false;
7086 else
7087 bFound = (fDataVal <= rItem.mfVal);
7089 else
7091 if (rItem.meType != ScQueryEntry::ByString)
7092 bFound = false;
7093 else
7094 bFound = (ScGlobal::GetCollator().compareString(aDataStr.getString(), rItem.maString.getString()) <= 0);
7097 if (!bFound)
7099 PushNA();
7100 return;
7103 if (pResMat)
7105 if (pResMat->IsValue( 0, 0 ))
7106 PushDouble(pResMat->GetDouble( 0, 0 ));
7107 else
7108 PushString(pResMat->GetString(0, 0));
7110 else if (nParamCount == 3)
7112 switch (eResArrayType)
7114 case svDouble:
7115 PushDouble( fResVal );
7116 break;
7117 case svString:
7118 PushString( aResStr );
7119 break;
7120 case svDoubleRef:
7121 case svSingleRef:
7122 PushCellResultToken( true, ScAddress( nResCol1, nResRow1, nResTab), nullptr, nullptr);
7123 break;
7124 default:
7125 assert(!"ScInterpreter::ScLookup: unhandled eResArrayType, single value data");
7126 PushIllegalParameter();
7129 else
7131 switch (eDataArrayType)
7133 case svDouble:
7134 PushDouble( fDataVal );
7135 break;
7136 case svString:
7137 PushString( aDataStr );
7138 break;
7139 case svSingleRef:
7140 PushCellResultToken( true, aDataAdr, nullptr, nullptr);
7141 break;
7142 default:
7143 assert(!"ScInterpreter::ScLookup: unhandled eDataArrayType, single value data");
7144 PushIllegalParameter();
7147 return;
7150 // Now, perform the search to compute the delta position (nDelta).
7152 if (pDataMat)
7154 // Data array is given as a matrix.
7155 rEntry.bDoQuery = true;
7156 rEntry.eOp = SC_LESS_EQUAL;
7157 bool bFound = false;
7159 SCSIZE nC, nR;
7160 pDataMat->GetDimensions(nC, nR);
7162 // Do not propagate errors from matrix while copying to vector.
7163 pDataMat->SetErrorInterpreter( nullptr);
7165 // Excel has an undocumented behaviour in that it seems to internally
7166 // sort an interim array (i.e. error values specifically #DIV/0! are
7167 // sorted to the end) or ignore error values that makes these "get last
7168 // non-empty" searches work, e.g. =LOOKUP(2,1/NOT(ISBLANK(A:A)),A:A)
7169 // see tdf#117016
7170 // Instead of sorting a million entries of which mostly only a bunch of
7171 // rows are filled and moving error values to the end which most are
7172 // already anyway, assume the matrix to be sorted except error values
7173 // and omit the coded DoubleError values.
7174 // Do this only for a numeric matrix (that includes errors coded as
7175 // doubles), which covers the case in question.
7176 /* TODO: it's unclear whether this really matches Excel behaviour in
7177 * all constellations or if there are cases that include unsorted error
7178 * values and thus yield arbitrary binary search results or something
7179 * different or whether there are cases where error values are also
7180 * omitted from mixed numeric/string arrays or if it's not an interim
7181 * matrix but a cell range reference instead. */
7182 const bool bOmitErrorValues = (eDataArrayType == svMatrix && pDataMat->IsNumeric());
7184 // In case of non-vector matrix, only search the first row or column.
7185 ScMatrixRef pDataMat2;
7186 std::vector<SCCOLROW> vIndex;
7187 if (bOmitErrorValues)
7189 std::vector<double> vArray;
7190 VectorMatrixAccessor aMatAcc(*pDataMat, bVertical);
7191 const SCSIZE nElements = aMatAcc.GetElementCount();
7192 for (SCSIZE i=0; i < nElements; ++i)
7194 const double fVal = aMatAcc.GetDouble(i);
7195 if (std::isfinite(fVal))
7197 vArray.push_back(fVal);
7198 vIndex.push_back(i);
7201 if (vArray.empty())
7203 PushNA();
7204 return;
7206 const size_t nElems = vArray.size();
7207 if (nElems == nElements)
7209 // No error value omitted, use as is.
7210 pDataMat2 = pDataMat;
7211 std::vector<SCCOLROW>().swap( vIndex);
7213 else
7215 nLenMajor = nElems;
7216 if (bVertical)
7218 ScMatrixRef pTempMat = GetNewMat( 1, nElems, /*bEmpty*/true );
7219 pTempMat->PutDoubleVector( vArray, 0, 0);
7220 pDataMat2 = std::move(pTempMat);
7222 else
7224 ScMatrixRef pTempMat = GetNewMat( nElems, 1, /*bEmpty*/true );
7225 for (size_t i=0; i < nElems; ++i)
7226 pTempMat->PutDouble( vArray[i], i, 0);
7227 pDataMat2 = std::move(pTempMat);
7231 else
7233 // Just use as is with the VectorMatrixAccessor.
7234 pDataMat2 = pDataMat;
7237 // Do not propagate errors from matrix while searching.
7238 pDataMat2->SetErrorInterpreter( nullptr);
7240 VectorMatrixAccessor aMatAcc2(*pDataMat2, bVertical);
7242 // binary search for non-equality mode (the source data is
7243 // assumed to be sorted in ascending order).
7245 SCCOLROW nDelta = -1;
7246 bool bMatchWholeCell = mrDoc.GetDocOptions().IsMatchWholeCell();
7248 SCSIZE nFirst = 0, nLast = nLenMajor-1; //, nHitIndex = 0;
7249 for (SCSIZE nLen = nLast-nFirst; nLen > 0; nLen = nLast-nFirst)
7251 SCSIZE nMid = nFirst + nLen/2;
7252 sal_Int32 nCmp = lcl_CompareMatrix2Query( nMid, aMatAcc2, aParam, rEntry, bMatchWholeCell );
7253 if (nCmp == 0)
7255 // exact match. find the last item with the same value.
7256 lcl_GetLastMatch( nMid, aMatAcc2, nLenMajor);
7257 nDelta = nMid;
7258 bFound = true;
7259 break;
7262 if (nLen == 1) // first and last items are next to each other.
7264 nDelta = nCmp < 0 ? nLast - 1 : nFirst - 1;
7265 // If already the 1st item is greater there's nothing found.
7266 bFound = (nDelta >= 0);
7267 break;
7270 if (nCmp < 0)
7271 nFirst = nMid;
7272 else
7273 nLast = nMid;
7276 if (nDelta == static_cast<SCCOLROW>(nLenMajor-2)) // last item
7278 sal_Int32 nCmp = lcl_CompareMatrix2Query(nDelta+1, aMatAcc2, aParam, rEntry, bMatchWholeCell );
7279 if (nCmp <= 0)
7281 // either the last item is an exact match or the real
7282 // hit is beyond the last item.
7283 nDelta += 1;
7284 bFound = true;
7287 else if (nDelta > 0) // valid hit must be 2nd item or higher
7289 // non-exact match
7290 bFound = true;
7293 // With 0-9 < A-Z, if query is numeric and data found is string, or
7294 // vice versa, the (yet another undocumented) Excel behavior is to
7295 // return #N/A instead.
7297 if (bFound)
7299 if (!vIndex.empty())
7300 nDelta = vIndex[nDelta];
7302 VectorMatrixAccessor aMatAcc(*pDataMat, bVertical);
7303 SCCOLROW i = nDelta;
7304 SCSIZE n = aMatAcc.GetElementCount();
7305 if (o3tl::make_unsigned(i) >= n)
7306 i = static_cast<SCCOLROW>(n);
7307 bool bByString = rEntry.GetQueryItem().meType == ScQueryEntry::ByString;
7308 if (bByString == aMatAcc.IsValue(i))
7309 bFound = false;
7312 if (!bFound)
7314 PushNA();
7315 return;
7318 // Now that we've found the delta, push the result back to the cell.
7320 if (pResMat)
7322 VectorMatrixAccessor aResMatAcc(*pResMat, bVertical);
7323 // Result array is matrix.
7324 // Note this does not replicate the other dimension.
7325 if (o3tl::make_unsigned(nDelta) >= aResMatAcc.GetElementCount())
7327 PushNA();
7328 return;
7330 if (aResMatAcc.IsValue(nDelta))
7331 PushDouble(aResMatAcc.GetDouble(nDelta));
7332 else
7333 PushString(aResMatAcc.GetString(nDelta));
7335 else if (nParamCount == 3)
7337 /* TODO: the entire switch is a copy of the cell range search
7338 * result, factor out. */
7339 switch (eResArrayType)
7341 case svDoubleRef:
7342 case svSingleRef:
7344 // Use the result array vector. Note that the result array is assumed
7345 // to be a vector (i.e. 1-dimensional array).
7347 ScAddress aAdr;
7348 aAdr.SetTab(nResTab);
7349 bool bResVertical = (nResRow2 - nResRow1) > 0;
7350 if (bResVertical)
7352 SCROW nTempRow = static_cast<SCROW>(nResRow1 + nDelta);
7353 if (nTempRow > mrDoc.MaxRow())
7355 PushDouble(0);
7356 return;
7358 aAdr.SetCol(nResCol1);
7359 aAdr.SetRow(nTempRow);
7361 else
7363 SCCOL nTempCol = static_cast<SCCOL>(nResCol1 + nDelta);
7364 if (nTempCol > mrDoc.MaxCol())
7366 PushDouble(0);
7367 return;
7369 aAdr.SetCol(nTempCol);
7370 aAdr.SetRow(nResRow1);
7372 PushCellResultToken( true, aAdr, nullptr, nullptr);
7374 break;
7375 case svDouble:
7376 case svString:
7378 if (nDelta != 0)
7379 PushNA();
7380 else
7382 switch (eResArrayType)
7384 case svDouble:
7385 PushDouble( fResVal );
7386 break;
7387 case svString:
7388 PushString( aResStr );
7389 break;
7390 default:
7391 ; // nothing
7395 break;
7396 default:
7397 assert(!"ScInterpreter::ScLookup: unhandled eResArrayType, array search");
7398 PushIllegalParameter();
7401 else
7403 // No result array. Use the data array to get the final value from.
7404 // Propagate errors from matrix again.
7405 pDataMat->SetErrorInterpreter( this);
7406 if (bVertical)
7408 if (pDataMat->IsValue(nC-1, nDelta))
7409 PushDouble(pDataMat->GetDouble(nC-1, nDelta));
7410 else
7411 PushString(pDataMat->GetString(nC-1, nDelta));
7413 else
7415 if (pDataMat->IsValue(nDelta, nR-1))
7416 PushDouble(pDataMat->GetDouble(nDelta, nR-1));
7417 else
7418 PushString(pDataMat->GetString(nDelta, nR-1));
7422 return;
7425 // Perform cell range search.
7427 aParam.nCol1 = nCol1;
7428 aParam.nRow1 = nRow1;
7429 aParam.nCol2 = bVertical ? nCol1 : nCol2;
7430 aParam.nRow2 = bVertical ? nRow2 : nRow1;
7431 aParam.bByRow = bVertical;
7433 rEntry.bDoQuery = true;
7434 rEntry.eOp = SC_LESS_EQUAL;
7435 rEntry.nField = nCol1;
7436 ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
7437 if (rItem.meType == ScQueryEntry::ByString)
7438 aParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc);
7440 ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, aParam, false, false);
7441 SCCOL nC;
7442 SCROW nR;
7443 // Advance Entry.nField in iterator upon switching columns if
7444 // lookup in row.
7445 aCellIter.SetAdvanceQueryParamEntryField(!bVertical);
7446 if ( !aCellIter.FindEqualOrSortedLastInRange(nC, nR) )
7448 PushNA();
7449 return;
7452 SCCOLROW nDelta = bVertical ? static_cast<SCSIZE>(nR-nRow1) : static_cast<SCSIZE>(nC-nCol1);
7454 if (pResMat)
7456 VectorMatrixAccessor aResMatAcc(*pResMat, bVertical);
7457 // Use the matrix result array.
7458 // Note this does not replicate the other dimension.
7459 if (o3tl::make_unsigned(nDelta) >= aResMatAcc.GetElementCount())
7461 PushNA();
7462 return;
7464 if (aResMatAcc.IsValue(nDelta))
7465 PushDouble(aResMatAcc.GetDouble(nDelta));
7466 else
7467 PushString(aResMatAcc.GetString(nDelta));
7469 else if (nParamCount == 3)
7471 /* TODO: the entire switch is a copy of the array search result, factor
7472 * out. */
7473 switch (eResArrayType)
7475 case svDoubleRef:
7476 case svSingleRef:
7478 // Use the result array vector. Note that the result array is assumed
7479 // to be a vector (i.e. 1-dimensional array).
7481 ScAddress aAdr;
7482 aAdr.SetTab(nResTab);
7483 bool bResVertical = (nResRow2 - nResRow1) > 0;
7484 if (bResVertical)
7486 SCROW nTempRow = static_cast<SCROW>(nResRow1 + nDelta);
7487 if (nTempRow > mrDoc.MaxRow())
7489 PushDouble(0);
7490 return;
7492 aAdr.SetCol(nResCol1);
7493 aAdr.SetRow(nTempRow);
7495 else
7497 SCCOL nTempCol = static_cast<SCCOL>(nResCol1 + nDelta);
7498 if (nTempCol > mrDoc.MaxCol())
7500 PushDouble(0);
7501 return;
7503 aAdr.SetCol(nTempCol);
7504 aAdr.SetRow(nResRow1);
7506 PushCellResultToken( true, aAdr, nullptr, nullptr);
7508 break;
7509 case svDouble:
7510 case svString:
7512 if (nDelta != 0)
7513 PushNA();
7514 else
7516 switch (eResArrayType)
7518 case svDouble:
7519 PushDouble( fResVal );
7520 break;
7521 case svString:
7522 PushString( aResStr );
7523 break;
7524 default:
7525 ; // nothing
7529 break;
7530 default:
7531 assert(!"ScInterpreter::ScLookup: unhandled eResArrayType, range search");
7532 PushIllegalParameter();
7535 else
7537 // Regardless of whether or not the result array exists, the last
7538 // array is always used as the "result" array.
7540 ScAddress aAdr;
7541 aAdr.SetTab(nTab1);
7542 if (bVertical)
7544 SCROW nTempRow = static_cast<SCROW>(nRow1 + nDelta);
7545 if (nTempRow > mrDoc.MaxRow())
7547 PushDouble(0);
7548 return;
7550 aAdr.SetCol(nCol2);
7551 aAdr.SetRow(nTempRow);
7553 else
7555 SCCOL nTempCol = static_cast<SCCOL>(nCol1 + nDelta);
7556 if (nTempCol > mrDoc.MaxCol())
7558 PushDouble(0);
7559 return;
7561 aAdr.SetCol(nTempCol);
7562 aAdr.SetRow(nRow2);
7564 PushCellResultToken(true, aAdr, nullptr, nullptr);
7568 void ScInterpreter::ScHLookup()
7570 CalculateLookup(true);
7573 void ScInterpreter::CalculateLookup(bool bHLookup)
7575 sal_uInt8 nParamCount = GetByte();
7576 if (!MustHaveParamCount(nParamCount, 3, 4))
7577 return;
7579 // Optional 4th argument to declare whether or not the range is sorted.
7580 bool bSorted = true;
7581 if (nParamCount == 4)
7582 bSorted = GetBool();
7584 // Index of column to search.
7585 double fIndex = ::rtl::math::approxFloor( GetDouble() ) - 1.0;
7587 ScMatrixRef pMat = nullptr;
7588 SCSIZE nC = 0, nR = 0;
7589 SCCOL nCol1 = 0;
7590 SCROW nRow1 = 0;
7591 SCTAB nTab1 = 0;
7592 SCCOL nCol2 = 0;
7593 SCROW nRow2 = 0;
7594 const ScComplexRefData* refData = nullptr;
7595 StackVar eType = GetStackType();
7596 if (eType == svDoubleRef)
7598 refData = GetStackDoubleRef(0);
7599 SCTAB nTab2;
7600 PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
7601 if (nTab1 != nTab2)
7603 PushIllegalParameter();
7604 return;
7607 else if (eType == svSingleRef)
7609 PopSingleRef(nCol1, nRow1, nTab1);
7610 nCol2 = nCol1;
7611 nRow2 = nRow1;
7613 else if (eType == svMatrix || eType == svExternalDoubleRef || eType == svExternalSingleRef)
7615 pMat = GetMatrix();
7617 if (pMat)
7618 pMat->GetDimensions(nC, nR);
7619 else
7621 PushIllegalParameter();
7622 return;
7625 else
7627 PushIllegalParameter();
7628 return;
7631 if ( fIndex < 0.0 || (bHLookup ? (pMat ? (fIndex >= nR) : (fIndex+nRow1 > nRow2)) : (pMat ? (fIndex >= nC) : (fIndex+nCol1 > nCol2)) ) )
7633 PushIllegalArgument();
7634 return;
7637 SCROW nZIndex = static_cast<SCROW>(fIndex);
7638 SCCOL nSpIndex = static_cast<SCCOL>(fIndex);
7640 if (!pMat)
7642 nZIndex += nRow1; // value row
7643 nSpIndex = sal::static_int_cast<SCCOL>( nSpIndex + nCol1 ); // value column
7646 if (nGlobalError != FormulaError::NONE)
7648 PushIllegalParameter();
7649 return;
7652 ScQueryParam aParam;
7653 aParam.nCol1 = nCol1;
7654 aParam.nRow1 = nRow1;
7655 if ( bHLookup )
7657 aParam.nCol2 = nCol2;
7658 aParam.nRow2 = nRow1; // search only in the first row
7659 aParam.bByRow = false;
7661 else
7663 aParam.nCol2 = nCol1; // search only in the first column
7664 aParam.nRow2 = nRow2;
7665 aParam.nTab = nTab1;
7668 ScQueryEntry& rEntry = aParam.GetEntry(0);
7669 rEntry.bDoQuery = true;
7670 if ( bSorted )
7671 rEntry.eOp = SC_LESS_EQUAL;
7672 if ( !FillEntry(rEntry) )
7673 return;
7675 ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
7676 svl::SharedString aParamStr;
7677 if (rItem.meType == ScQueryEntry::ByString)
7679 aParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc);
7680 aParamStr = rItem.maString;
7683 if (pMat)
7685 SCSIZE nMatCount = bHLookup ? nC : nR;
7686 SCSIZE nDelta = SCSIZE_MAX;
7687 if (rItem.meType == ScQueryEntry::ByString)
7689 //!!!!!!!
7690 //TODO: enable regex on matrix strings
7691 //!!!!!!!
7692 if ( bSorted )
7694 CollatorWrapper& rCollator = ScGlobal::GetCollator();
7695 for (SCSIZE i = 0; i < nMatCount; i++)
7697 if (bHLookup ? pMat->IsStringOrEmpty(i, 0) : pMat->IsStringOrEmpty(0, i))
7699 sal_Int32 nRes =
7700 rCollator.compareString(
7701 bHLookup ? pMat->GetString(i,0).getString() : pMat->GetString(0,i).getString(), aParamStr.getString());
7702 if (nRes <= 0)
7703 nDelta = i;
7704 else if (i>0) // #i2168# ignore first mismatch
7705 i = nMatCount+1;
7707 else
7708 nDelta = i;
7711 else
7713 if (bHLookup)
7715 for (SCSIZE i = 0; i < nMatCount; i++)
7717 if (pMat->IsStringOrEmpty(i, 0))
7719 if (pMat->GetString(i,0).getDataIgnoreCase() == aParamStr.getDataIgnoreCase())
7721 nDelta = i;
7722 i = nMatCount + 1;
7727 else
7729 nDelta = pMat->MatchStringInColumns(aParamStr, 0, 0);
7733 else
7735 if ( bSorted )
7737 // #i2168# ignore strings
7738 for (SCSIZE i = 0; i < nMatCount; i++)
7740 if (!(bHLookup ? pMat->IsStringOrEmpty(i, 0) : pMat->IsStringOrEmpty(0, i)))
7742 if ((bHLookup ? pMat->GetDouble(i,0) : pMat->GetDouble(0,i)) <= rItem.mfVal)
7743 nDelta = i;
7744 else
7745 i = nMatCount+1;
7749 else
7751 if (bHLookup)
7753 for (SCSIZE i = 0; i < nMatCount; i++)
7755 if (! pMat->IsStringOrEmpty(i, 0) )
7757 if ( pMat->GetDouble(i,0) == rItem.mfVal)
7759 nDelta = i;
7760 i = nMatCount + 1;
7765 else
7767 nDelta = pMat->MatchDoubleInColumns(rItem.mfVal, 0, 0);
7771 if ( nDelta != SCSIZE_MAX )
7773 SCSIZE nX = static_cast<SCSIZE>(nSpIndex);
7774 SCSIZE nY = nDelta;
7775 SCSIZE nXs = 0;
7776 SCSIZE nYs = nY;
7777 if ( bHLookup )
7779 nX = nDelta;
7780 nY = static_cast<SCSIZE>(nZIndex);
7781 nXs = nX;
7782 nYs = 0;
7784 assert( nX < nC && nY < nR );
7785 if (!(rItem.meType == ScQueryEntry::ByString && pMat->IsValue( nXs, nYs)))
7787 if (pMat->IsStringOrEmpty( nX, nY))
7788 PushString(pMat->GetString( nX, nY).getString());
7789 else
7790 PushDouble(pMat->GetDouble( nX, nY));
7792 else
7793 PushNA();
7794 return;
7796 else
7797 PushNA();
7799 else
7801 // not a matrix
7802 rEntry.nField = nCol1;
7803 bool bFound = false;
7804 SCCOL nCol = 0;
7805 SCROW nRow = 0;
7806 if ( bSorted )
7807 rEntry.eOp = SC_LESS_EQUAL;
7808 if ( bHLookup )
7810 ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, aParam, false, false);
7811 // advance Entry.nField in Iterator upon switching columns
7812 aCellIter.SetAdvanceQueryParamEntryField( true );
7813 if ( bSorted )
7815 SCROW nRow1_temp;
7816 bFound = aCellIter.FindEqualOrSortedLastInRange( nCol, nRow1_temp );
7818 else if ( aCellIter.GetFirst() )
7820 bFound = true;
7821 nCol = aCellIter.GetCol();
7823 nRow = nZIndex;
7825 else
7827 ScAddress aResultPos( nCol1, nRow1, nTab1);
7828 bFound = LookupQueryWithCache( aResultPos, aParam, refData, 1 /*searchfwd*/, SC_OPCODE_V_LOOKUP );
7829 nRow = aResultPos.Row();
7830 nCol = nSpIndex;
7833 if ( bFound )
7835 ScAddress aAdr( nCol, nRow, nTab1 );
7836 PushCellResultToken( true, aAdr, nullptr, nullptr);
7838 else
7839 PushNA();
7843 bool ScInterpreter::FillEntry(ScQueryEntry& rEntry)
7845 ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
7846 switch ( GetStackType() )
7848 case svDouble:
7850 rItem.meType = ScQueryEntry::ByValue;
7851 rItem.mfVal = GetDouble();
7853 break;
7854 case svString:
7856 rItem.meType = ScQueryEntry::ByString;
7857 rItem.maString = GetString();
7859 break;
7860 case svDoubleRef :
7861 case svSingleRef :
7863 ScAddress aAdr;
7864 if ( !PopDoubleRefOrSingleRef( aAdr ) )
7866 PushInt(0);
7867 return false;
7869 ScRefCellValue aCell(mrDoc, aAdr);
7870 if (aCell.hasNumeric())
7872 rItem.meType = ScQueryEntry::ByValue;
7873 rItem.mfVal = GetCellValue(aAdr, aCell);
7875 else
7877 GetCellString(rItem.maString, aCell);
7878 rItem.meType = ScQueryEntry::ByString;
7881 break;
7882 case svExternalDoubleRef:
7883 case svExternalSingleRef:
7884 case svMatrix:
7886 svl::SharedString aStr;
7887 const ScMatValType nType = GetDoubleOrStringFromMatrix(rItem.mfVal, aStr);
7888 rItem.maString = std::move(aStr);
7889 rItem.meType = ScMatrix::IsNonValueType(nType) ?
7890 ScQueryEntry::ByString : ScQueryEntry::ByValue;
7892 break;
7893 default:
7895 PushIllegalParameter();
7896 return false;
7898 } // switch ( GetStackType() )
7899 return true;
7902 void ScInterpreter::ScVLookup()
7904 CalculateLookup(false);
7907 void ScInterpreter::ScXLookup()
7909 /* TODO
7910 -use VectorSearchArguments and SearchVectorForValue() with ScLookup, ScHLookup and ScVLookup
7911 as well to reduce redundant code, can de done later with lots of other MATCH/LOOKUP related code
7912 that can be unified
7913 -BinarySearch not supported for columns (horizontal search), now just use linear mode in this case
7914 -improve efficiency of code
7916 sal_uInt8 nParamCount = GetByte();
7917 if ( !MustHaveParamCount( nParamCount, 3, 6 ) )
7918 return;
7920 VectorSearchArguments vsa;
7921 vsa.nSearchOpCode = SC_OPCODE_X_LOOKUP;
7923 if ( nParamCount == 6 )
7925 sal_Int16 k = GetInt16();
7926 if ( k >= -2 && k <= 2 && k != 0 )
7927 vsa.eSearchMode = static_cast<SearchMode>(k);
7928 else
7930 PushIllegalParameter();
7931 return;
7934 else
7935 vsa.eSearchMode = searchfwd;
7937 if ( nParamCount >= 5 )
7939 sal_Int16 k = GetInt16();
7940 if ( k >= -1 && k <= 3 )
7941 vsa.eMatchMode = static_cast<MatchMode>(k);
7942 else
7944 PushIllegalParameter();
7945 return;
7948 else
7949 vsa.eMatchMode = exactorNA;
7951 // Optional 4th argument to set return values if not found (default is #N/A)
7952 formula::FormulaConstTokenRef xNotFound;
7953 FormulaError nFirstMatchError = FormulaError::NONE;
7954 if ( nParamCount >= 4 && GetStackType() != svEmptyCell )
7956 xNotFound = PopToken();
7957 nFirstMatchError = xNotFound->GetError();
7958 nGlobalError = FormulaError::NONE; // propagate only for match or active result path
7961 // 3rd argument is return value array
7962 ScMatrixRef prMat = nullptr;
7963 SCCOL nSearchCol1 = 0;
7964 SCROW nSearchRow1 = 0;
7965 SCTAB nSearchTab1 = 0;
7966 SCCOL nSearchCol2 = 0;
7967 SCROW nSearchRow2 = 0;
7968 SCTAB nSearchTab2 = 0;
7969 SCSIZE nrC = 0, nrR = 0;
7971 switch ( GetStackType() )
7973 case svSingleRef :
7974 PopSingleRef(nSearchCol1, nSearchRow1, nSearchTab1);
7975 nSearchCol2 = nSearchCol1;
7976 nSearchRow2 = nSearchRow1;
7977 nrC = nSearchCol2 - nSearchCol1 + 1;
7978 nrR = nSearchRow2 - nSearchRow1 + 1;
7979 break;
7980 case svDoubleRef:
7982 PopDoubleRef(nSearchCol1, nSearchRow1, nSearchTab1, nSearchCol2, nSearchRow2, nSearchTab2);
7983 if (nSearchTab1 != nSearchTab2)
7985 PushIllegalParameter();
7986 return;
7988 nrC = nSearchCol2 - nSearchCol1 + 1;
7989 nrR = nSearchRow2 - nSearchRow1 + 1;
7991 break;
7992 case svMatrix :
7993 case svExternalDoubleRef :
7995 if (GetStackType() == svMatrix)
7996 prMat = PopMatrix();
7997 else
7998 PopExternalDoubleRef(prMat);
8000 if (!prMat)
8002 PushIllegalParameter();
8003 return;
8005 prMat->GetDimensions(nrC, nrR);
8007 break;
8009 default :
8010 PushIllegalParameter();
8011 return;
8014 // 2nd argument is vector to be searched
8015 SCSIZE nsC = 0, nsR = 0;
8016 switch ( GetStackType() )
8018 case svSingleRef:
8019 vsa.pMatSrc = nullptr;
8020 PopSingleRef( vsa.nCol1, vsa.nRow1, vsa.nTab1);
8021 vsa.nCol2 = vsa.nCol1;
8022 vsa.nRow2 = vsa.nRow1;
8023 nsC = vsa.nCol2 - vsa.nCol1 + 1;
8024 nsR = vsa.nRow2 - vsa.nRow1 + 1;
8025 break;
8026 case svDoubleRef:
8028 vsa.pMatSrc = nullptr;
8029 SCTAB nTab2 = 0;
8030 PopDoubleRef(vsa.nCol1, vsa.nRow1, vsa.nTab1, vsa.nCol2, vsa.nRow2, nTab2);
8031 if (vsa.nTab1 != nTab2 || (vsa.nCol1 != vsa.nCol2 && vsa.nRow1 != vsa.nRow2))
8033 PushIllegalParameter();
8034 return;
8036 nsC = vsa.nCol2 - vsa.nCol1 + 1;
8037 nsR = vsa.nRow2 - vsa.nRow1 + 1;
8039 break;
8040 case svMatrix:
8041 case svExternalDoubleRef:
8043 if (GetStackType() == svMatrix)
8044 vsa.pMatSrc = PopMatrix();
8045 else
8046 PopExternalDoubleRef(vsa.pMatSrc);
8048 if (!vsa.pMatSrc)
8050 PushIllegalParameter();
8051 return;
8053 vsa.pMatSrc->GetDimensions( nsC, nsR);
8055 break;
8057 default:
8058 PushIllegalParameter();
8059 return;
8061 if ( ( nsR >= nsC && nsR != nrR ) || ( nsR < nsC && nsC != nrC ) )
8063 // search matrix must have same number of elements as result matrix in search direction
8064 PushIllegalParameter();
8065 return;
8068 // 1st argument is search value
8069 if (nGlobalError == FormulaError::NONE)
8071 switch ( GetRawStackType() )
8073 case svMissing:
8074 case svEmptyCell:
8076 vsa.isEmptySearch = true;
8077 vsa.isStringSearch = false;
8078 vsa.sSearchStr = GetString();
8080 break;
8082 case svDouble:
8084 vsa.isStringSearch = false;
8085 vsa.fSearchVal = GetDouble();
8087 break;
8089 case svString:
8091 vsa.isStringSearch = true;
8092 vsa.sSearchStr = GetString();
8094 break;
8096 case svDoubleRef :
8097 case svSingleRef :
8099 ScAddress aAdr;
8100 if ( !PopDoubleRefOrSingleRef( aAdr ) )
8102 PushInt(0);
8103 return ;
8105 ScRefCellValue aCell(mrDoc, aAdr);
8106 if (aCell.hasNumeric())
8108 vsa.isStringSearch = false;
8109 vsa.fSearchVal = GetCellValue(aAdr, aCell);
8111 else
8113 vsa.isStringSearch = true;
8114 GetCellString(vsa.sSearchStr, aCell);
8117 break;
8119 case svExternalSingleRef:
8121 ScExternalRefCache::TokenRef pToken;
8122 PopExternalSingleRef(pToken);
8123 if (nGlobalError != FormulaError::NONE)
8125 PushError( nGlobalError);
8126 return;
8128 if (pToken->GetType() == svDouble)
8130 vsa.isStringSearch = false;
8131 vsa.fSearchVal = pToken->GetDouble();
8133 else
8135 vsa.isStringSearch = true;
8136 vsa.sSearchStr = pToken->GetString();
8139 break;
8141 case svExternalDoubleRef:
8142 case svMatrix :
8144 ScMatValType nType = GetDoubleOrStringFromMatrix(
8145 vsa.fSearchVal, vsa.sSearchStr);
8146 vsa.isStringSearch = ScMatrix::IsNonValueType(nType);
8148 break;
8150 default:
8152 PushIllegalParameter();
8153 return;
8158 // start search
8159 if ( SearchVectorForValue( vsa ) )
8161 // found, output result
8162 assert( vsa.bVLookup ? ( o3tl::make_unsigned(vsa.nIndex) < nrR ) :
8163 ( o3tl::make_unsigned(vsa.nIndex) < nrC ) );
8164 SCSIZE nX;
8165 SCSIZE nY;
8166 SCSIZE nResCols;
8167 SCSIZE nResRows;
8168 if ( vsa.bVLookup )
8170 nX = static_cast<SCSIZE>(0);
8171 nY = vsa.nIndex;
8172 nResCols = nrC;
8173 nResRows = 1;
8175 else
8177 nX = vsa.nIndex;
8178 nY = static_cast<SCSIZE>(0);
8179 nResCols = 1;
8180 nResRows = nrR;
8182 // if result has more than one row or column push double ref or matrix, else push single ref
8183 if ( nResCols > 1 || nResRows > 1 )
8185 if (prMat)
8187 // result is matrix, make / fill matrix with output and push that
8188 ScMatrixRef pResMat = GetNewMat(nResCols, nResRows, /*bEmpty*/true);
8189 if (pResMat)
8191 for (SCSIZE i = 0; i < nResCols; i++)
8193 for (SCSIZE j = 0; j < nResRows; j++)
8195 SCSIZE ri;
8196 SCSIZE rj;
8197 if (vsa.bVLookup)
8199 ri = nX + i;
8200 rj = nY;
8202 else
8204 ri = nX;
8205 rj = nY + j;
8207 if (prMat->IsEmptyCell(ri, rj))
8208 pResMat->PutEmpty(i, j);
8209 else if (prMat->IsStringOrEmpty(ri, rj))
8210 pResMat->PutString(prMat->GetString(ri, rj), i, j);
8211 else
8212 pResMat->PutDouble(prMat->GetDouble(ri, rj), i, j);
8215 PushMatrix(pResMat);
8217 else
8219 PushIllegalParameter();
8220 return;
8223 else
8225 // result is a double ref
8226 PushDoubleRef(nSearchCol1 + nX, nSearchRow1 + nY, nSearchTab1,
8227 nSearchCol1 + (nResCols - 1) + nX, nSearchRow1 + (nResRows - 1) + nY, nSearchTab1);
8230 else
8232 // result is a single ref
8233 PushSingleRef(nSearchCol1 + nX, nSearchRow1 + nY, nSearchTab1);
8236 else
8238 if ( vsa.isResultNA )
8240 if ( xNotFound && ( xNotFound->GetType() != svMissing ) )
8242 nGlobalError = nFirstMatchError;
8243 PushTokenRef(xNotFound);
8245 else
8246 PushNA();
8251 void ScInterpreter::ScFilter()
8253 sal_uInt8 nParamCount = GetByte();
8254 if (!MustHaveParamCount(nParamCount, 2, 3))
8255 return;
8257 // Optional 3th argument to set the value to return if all values
8258 // in the included array are empty (filter returns nothing)
8259 formula::FormulaConstTokenRef xNotFound;
8260 if (nParamCount == 3 && GetStackType() != svEmptyCell)
8261 xNotFound = PopToken();
8263 SCCOL nCondResultColEnd = 0;
8264 SCROW nCondResultRowEnd = 0;
8265 ScMatrixRef pCondResultMatrix = nullptr;
8266 std::vector<double> aResValues;
8267 size_t nMatch = 0;
8268 // take 2nd argument criteria bool array
8269 switch ( GetStackType() )
8271 case svMatrix :
8272 case svExternalDoubleRef:
8273 case svExternalSingleRef:
8274 case svDoubleRef:
8275 case svSingleRef:
8277 pCondResultMatrix = GetMatrix();
8278 if (!pCondResultMatrix)
8280 PushError(FormulaError::IllegalParameter);
8281 return;
8283 SCSIZE nC, nR;
8284 pCondResultMatrix->GetDimensions(nC, nR);
8285 nCondResultColEnd = static_cast<SCCOL>(nC - 1);
8286 nCondResultRowEnd = static_cast<SCROW>(nR - 1);
8288 // only 1 dimension of filtering allowed (also in excel)
8289 if (nCondResultColEnd > 0 && nCondResultRowEnd > 0)
8291 PushError(FormulaError::NoValue);
8292 return;
8295 // result matrix is filled with boolean values.
8296 pCondResultMatrix->GetDoubleArray(aResValues);
8298 FormulaError nError = FormulaError::NONE;
8299 auto matchNum = [&nMatch, &nError](double i) {
8300 nError = GetDoubleErrorValue(i);
8301 if (nError != FormulaError::NONE)
8303 return true;
8305 else
8307 if (i > 0)
8308 nMatch++;
8309 return false;
8313 if (auto it = std::find_if(aResValues.begin(), aResValues.end(), matchNum); it != aResValues.end())
8315 PushError(nError);
8316 return;
8319 break;
8321 default:
8323 PushIllegalParameter();
8324 return;
8328 // bail out, no need to evaluate other arguments
8329 if (nGlobalError != FormulaError::NONE)
8331 PushError(nGlobalError);
8332 return;
8335 SCCOL nQueryCol1 = 0;
8336 SCROW nQueryRow1 = 0;
8337 SCCOL nQueryCol2 = 0;
8338 SCROW nQueryRow2 = 0;
8339 ScMatrixRef pQueryMatrix = nullptr;
8340 // take 1st argument range
8341 switch ( GetStackType() )
8343 case svSingleRef:
8344 case svDoubleRef:
8345 case svMatrix:
8346 case svExternalSingleRef:
8347 case svExternalDoubleRef:
8349 pQueryMatrix = GetMatrix();
8350 if (!pQueryMatrix)
8352 PushError( FormulaError::IllegalParameter);
8353 return;
8355 SCSIZE nC, nR;
8356 pQueryMatrix->GetDimensions( nC, nR);
8357 nQueryCol2 = static_cast<SCCOL>(nC - 1);
8358 nQueryRow2 = static_cast<SCROW>(nR - 1);
8360 break;
8361 default:
8362 PushError( FormulaError::IllegalParameter);
8363 return;
8366 // bail out, no need to set a matrix if we have no result
8367 if (!nMatch)
8369 if (xNotFound && (xNotFound->GetType() != svMissing))
8370 PushTokenRef(xNotFound);
8371 else
8372 PushError(FormulaError::NestedArray);
8373 return;
8376 SCSIZE nResPos = 0;
8377 ScMatrixRef pResMat = nullptr;
8378 if (nQueryCol2 == nCondResultColEnd && nCondResultColEnd > 0)
8380 pResMat = GetNewMat(nMatch, nQueryRow2 + 1 , /*bEmpty*/true);
8381 for (SCROW iR = nQueryRow1; iR <= nQueryRow2; iR++)
8383 for (size_t iC = 0; iC < aResValues.size(); iC++)
8385 if (aResValues[iC] > 0)
8387 if (pQueryMatrix->IsEmptyCell(iC, iR))
8388 pResMat->PutEmptyTrans(nResPos++);
8389 else if (pQueryMatrix->IsStringOrEmpty(iC, iR))
8390 pResMat->PutStringTrans(pQueryMatrix->GetString(iC, iR), nResPos++);
8391 else
8392 pResMat->PutDoubleTrans(pQueryMatrix->GetDouble(iC, iR), nResPos++);
8397 else if (nQueryRow2 == nCondResultRowEnd && nCondResultRowEnd > 0)
8399 pResMat = GetNewMat(nQueryCol2 + 1, nMatch, /*bEmpty*/true);
8400 for (SCCOL iC = nQueryCol1; iC <= nQueryCol2; iC++)
8402 for (size_t iR = 0; iR < aResValues.size(); iR++)
8404 if (aResValues[iR] > 0)
8406 if (pQueryMatrix->IsEmptyCell(iC, iR))
8407 pResMat->PutEmpty(nResPos++);
8408 else if (pQueryMatrix->IsStringOrEmpty(iC, iR))
8409 pResMat->PutString(pQueryMatrix->GetString(iC, iR), nResPos++);
8410 else
8411 pResMat->PutDouble(pQueryMatrix->GetDouble(iC, iR), nResPos++);
8416 else
8418 PushError(FormulaError::IllegalParameter);
8419 return;
8422 if (pResMat)
8423 PushMatrix(pResMat);
8424 else
8425 PushError(FormulaError::NestedArray);
8428 void ScInterpreter::ScSort()
8430 sal_uInt8 nParamCount = GetByte();
8431 if (!MustHaveParamCount(nParamCount, 1, 4))
8432 return;
8434 // Create sort data
8435 ScSortParam aSortData;
8437 // 4th argument optional
8438 aSortData.bByRow = true; // default: By_Col = false --> bByRow = true
8439 if (nParamCount == 4)
8440 aSortData.bByRow = !GetBool();
8442 // 3rd argument optional
8443 std::vector<double> aSortOrderValues{ 1.0 }; // default: 1 = asc, -1 = desc
8444 if (nParamCount >= 3)
8446 bool bMissing = IsMissing();
8447 ScMatrixRef pSortOrder = GetMatrix();
8448 if (!bMissing)
8450 aSortOrderValues.clear();
8451 pSortOrder->GetDoubleArray(aSortOrderValues);
8452 for (const double& sortOrder : aSortOrderValues)
8454 if (sortOrder != 1.0 && sortOrder != -1.0)
8456 PushIllegalParameter();
8457 return;
8463 // 2nd argument optional
8464 std::vector<double> aSortIndexValues{ 0.0 }; // default: first column or row
8465 if (nParamCount >= 2)
8467 bool bMissing = IsMissing();
8468 ScMatrixRef pSortIndex = GetMatrix();
8469 if (!bMissing)
8471 aSortIndexValues.clear();
8472 pSortIndex->GetDoubleArray(aSortIndexValues);
8473 for (double& sortIndex : aSortIndexValues)
8475 if (sortIndex < 1)
8477 PushIllegalParameter();
8478 return;
8480 else
8481 sortIndex--;
8486 if (aSortIndexValues.size() != aSortOrderValues.size() && aSortOrderValues.size() > 1)
8488 PushIllegalParameter();
8489 return;
8492 // 1st argument is vector to be sorted
8493 SCSIZE nsC = 0, nsR = 0;
8494 ScMatrixRef pMatSrc = nullptr;
8495 switch ( GetStackType() )
8497 case svSingleRef:
8498 PopSingleRef(aSortData.nCol1, aSortData.nRow1, aSortData.nSourceTab);
8499 aSortData.nCol2 = aSortData.nCol1;
8500 aSortData.nRow2 = aSortData.nRow1;
8501 nsC = aSortData.nCol2 - aSortData.nCol1 + 1;
8502 nsR = aSortData.nRow2 - aSortData.nRow1 + 1;
8503 break;
8504 case svDoubleRef:
8506 SCTAB nTab2 = 0;
8507 PopDoubleRef(aSortData.nCol1, aSortData.nRow1, aSortData.nSourceTab, aSortData.nCol2, aSortData.nRow2, nTab2);
8508 if (aSortData.nSourceTab != nTab2)
8510 PushIllegalParameter();
8511 return;
8513 nsC = aSortData.nCol2 - aSortData.nCol1 + 1;
8514 nsR = aSortData.nRow2 - aSortData.nRow1 + 1;
8516 break;
8517 case svMatrix:
8518 case svExternalSingleRef:
8519 case svExternalDoubleRef:
8521 pMatSrc = GetMatrix();
8522 if (!pMatSrc)
8524 PushIllegalParameter();
8525 return;
8527 pMatSrc->GetDimensions(nsC, nsR);
8528 if (nsC == 0 || nsR == 0)
8530 PushIllegalArgument();
8531 return;
8533 aSortData.nCol2 = nsC - 1; // aSortData.nCol1 = 0
8534 aSortData.nRow2 = nsR - 1; // aSortData.nRow1 = 0
8536 break;
8538 default:
8539 PushIllegalParameter();
8540 return;
8543 aSortData.maKeyState.resize(aSortIndexValues.size());
8544 for (size_t i = 0; i < aSortIndexValues.size(); i++)
8546 SCSIZE fIndex = static_cast<SCSIZE>(double_to_int32(aSortIndexValues[i]));
8547 if ((aSortData.bByRow && fIndex + 1 > nsC) || (!aSortData.bByRow && fIndex + 1 > nsR))
8549 PushIllegalParameter();
8550 return;
8553 aSortData.maKeyState[i].bDoSort = true;
8554 aSortData.maKeyState[i].nField = (aSortData.bByRow ? aSortData.nCol1 : aSortData.nRow1) + fIndex;
8556 if (aSortIndexValues.size() == aSortOrderValues.size())
8557 aSortData.maKeyState[i].bAscending = (aSortOrderValues[i] == 1.0);
8558 else
8559 aSortData.maKeyState[i].bAscending = (aSortOrderValues[0] == 1.0);
8562 // sorting...
8563 std::vector<SCCOLROW> aOrderIndices = GetSortOrder(aSortData, pMatSrc);
8564 // create sorted matrix
8565 ScMatrixRef pResMat = CreateSortedMatrix(aSortData, pMatSrc,
8566 ScRange(aSortData.nCol1, aSortData.nRow1, aSortData.nSourceTab,
8567 aSortData.nCol2, aSortData.nRow2, aSortData.nSourceTab),
8568 aOrderIndices, nsC, nsR);
8570 if (pResMat)
8571 PushMatrix(pResMat);
8572 else
8573 PushIllegalParameter();
8576 void ScInterpreter::ScSortBy()
8578 sal_uInt8 nParamCount = GetByte();
8580 if (nParamCount < 2/*|| (nParamCount % 2 != 1)*/)
8582 PushError(FormulaError::ParameterExpected);
8583 return;
8586 sal_uInt8 nSortCount = nParamCount / 2;
8588 ScSortParam aSortData;
8589 aSortData.maKeyState.resize(nSortCount);
8591 // 127th, ..., 3rd and 2nd argument: sort by range/array and sort orders pair
8592 sal_uInt8 nSortBy = nSortCount;
8593 ScMatrixRef pFullMatSortBy = nullptr;
8594 while (nSortBy-- > 0 && nGlobalError == FormulaError::NONE)
8596 // 3rd argument sort_order optional: default ascending
8597 if (nParamCount >= 3 && (nParamCount % 2 == 1))
8599 sal_Int8 nSortOrder = static_cast<sal_Int8>(GetInt32WithDefault(1));
8600 if (nSortOrder != 1 && nSortOrder != -1)
8602 PushIllegalParameter();
8603 return;
8605 aSortData.maKeyState[nSortBy].bAscending = (nSortOrder == 1);
8606 nParamCount--;
8609 // 2nd argument: take sort by ranges
8610 ScMatrixRef pMatSortBy = nullptr;
8611 SCSIZE nbyC = 0, nbyR = 0;
8612 switch (GetStackType())
8614 case svSingleRef:
8615 case svDoubleRef:
8616 case svMatrix:
8617 case svExternalSingleRef:
8618 case svExternalDoubleRef:
8620 if (nSortCount == 1)
8622 pFullMatSortBy = GetMatrix();
8623 if (!pFullMatSortBy)
8625 PushIllegalParameter();
8626 return;
8628 pFullMatSortBy->GetDimensions(nbyC, nbyR);
8630 else
8632 pMatSortBy = GetMatrix();
8633 if (!pMatSortBy)
8635 PushIllegalParameter();
8636 return;
8638 pMatSortBy->GetDimensions(nbyC, nbyR);
8641 // last->first (backward) sortby array
8642 if (nSortBy == nSortCount - 1)
8644 if (nbyC == 1 && nbyR > 1)
8645 aSortData.bByRow = true;
8646 else if (nbyR == 1 && nbyC > 1)
8647 aSortData.bByRow = false;
8648 else
8650 PushIllegalParameter();
8651 return;
8654 if (nSortCount > 1)
8656 pFullMatSortBy = GetNewMat(aSortData.bByRow ? (nbyC * nSortCount) : nbyC,
8657 aSortData.bByRow ? nbyR : (nbyR * nSortCount), /*bEmpty*/true);
8661 break;
8663 default:
8664 PushIllegalParameter();
8665 return;
8668 // ..., penultimate sortby arrays
8669 if (nSortCount > 1 && nSortBy <= nSortCount - 1)
8671 SCSIZE nCheckCol = 0, nCheckRow = 0;
8672 pFullMatSortBy->GetDimensions(nCheckCol, nCheckRow);
8673 if ((aSortData.bByRow && nbyR == nCheckRow && nbyC == 1) ||
8674 (!aSortData.bByRow && nbyC == nCheckCol && nbyR == 1))
8676 for (SCSIZE ci = 0; ci < nbyC; ci++)//col
8678 for (SCSIZE rj = 0; rj < nbyR; rj++)//row
8680 if (pMatSortBy->IsEmptyCell(ci, rj))
8682 if (aSortData.bByRow)
8683 pFullMatSortBy->PutEmpty(ci + nSortBy, rj);
8684 else
8685 pFullMatSortBy->PutEmpty(ci, rj + nSortBy);
8687 else if (pMatSortBy->IsStringOrEmpty(ci, rj))
8689 if (aSortData.bByRow)
8690 pFullMatSortBy->PutString(pMatSortBy->GetString(ci, rj), ci + nSortBy, rj);
8691 else
8692 pFullMatSortBy->PutString(pMatSortBy->GetString(ci, rj), ci, rj + nSortBy);
8694 else
8696 if (aSortData.bByRow)
8697 pFullMatSortBy->PutDouble(pMatSortBy->GetDouble(ci, rj), ci + nSortBy, rj);
8698 else
8699 pFullMatSortBy->PutDouble(pMatSortBy->GetDouble(ci, rj), ci, rj + nSortBy);
8704 else
8706 PushIllegalParameter();
8707 return;
8711 aSortData.maKeyState[nSortBy].bDoSort = true;
8712 aSortData.maKeyState[nSortBy].nField = nSortBy;
8714 nParamCount--;
8717 // 1st argument is the range/array to be sorted
8718 SCSIZE nsC = 0, nsR = 0;
8719 SCCOL nSortCol1 = 0, nSortCol2 = 0;
8720 SCROW nSortRow1 = 0, nSortRow2 = 0;
8721 SCTAB nSortTab1 = 0, nSortTab2 = 0;
8722 ScMatrixRef pMatSrc = nullptr;
8723 switch ( GetStackType() )
8725 case svSingleRef:
8726 PopSingleRef(nSortCol1, nSortRow1, nSortTab1);
8727 nSortCol2 = nSortCol1;
8728 nSortRow2 = nSortRow1;
8729 nsC = nSortCol2 - nSortCol1 + 1;
8730 nsR = nSortRow2 - nSortRow1 + 1;
8731 break;
8732 case svDoubleRef:
8734 PopDoubleRef(nSortCol1, nSortRow1, nSortTab1, nSortCol2, nSortRow2, nSortTab2);
8735 if (nSortTab1 != nSortTab2)
8737 PushIllegalParameter();
8738 return;
8740 nsC = nSortCol2 - nSortCol1 + 1;
8741 nsR = nSortRow2 - nSortRow1 + 1;
8743 break;
8744 case svMatrix:
8745 case svExternalSingleRef:
8746 case svExternalDoubleRef:
8748 pMatSrc = GetMatrix();
8749 if (!pMatSrc)
8751 PushIllegalParameter();
8752 return;
8754 pMatSrc->GetDimensions(nsC, nsR);
8755 if (nsC == 0 || nsR == 0)
8757 PushIllegalArgument();
8758 return;
8760 nSortCol2 = nsC - 1; // nSortCol1 = 0
8761 nSortRow2 = nsR - 1; // nSortRow1 = 0
8763 break;
8765 default:
8766 PushIllegalParameter();
8767 return;
8770 SCSIZE nCheckMatrixCol = 0, nCheckMatrixRow = 0;
8771 pFullMatSortBy->GetDimensions(nCheckMatrixCol, nCheckMatrixRow);
8772 if (nGlobalError != FormulaError::NONE)
8774 PushError(nGlobalError);
8775 return;
8777 else if ((aSortData.bByRow && nsR != nCheckMatrixRow) ||
8778 (!aSortData.bByRow && nsC != nCheckMatrixCol))
8780 PushIllegalParameter();
8781 return;
8783 else
8785 aSortData.nCol2 = nCheckMatrixCol - 1;
8786 aSortData.nRow2 = nCheckMatrixRow - 1;
8789 // sorting...
8790 std::vector<SCCOLROW> aOrderIndices = GetSortOrder(aSortData, pFullMatSortBy);
8791 // create sorted matrix
8792 ScMatrixRef pResMat = CreateSortedMatrix(aSortData, pMatSrc,
8793 ScRange(nSortCol1, nSortRow1, nSortTab1, nSortCol2, nSortRow2, nSortTab2),
8794 aOrderIndices, nsC, nsR);
8796 if (pResMat)
8797 PushMatrix(pResMat);
8798 else
8799 PushIllegalParameter();
8802 void ScInterpreter::ScUnique()
8804 sal_uInt8 nParamCount = GetByte();
8805 if (!MustHaveParamCount(nParamCount, 1, 3))
8806 return;
8808 // 3rd argument optional - Exactly_once: default FALSE
8809 bool bExactly_once = false;
8810 if (nParamCount == 3)
8811 bExactly_once = GetBoolWithDefault(false);
8813 // 2nd argument optional - default: By_Col = false --> bByRow = true
8814 bool bByRow = true;
8815 if (nParamCount >= 2)
8816 bByRow = !GetBoolWithDefault(false);
8818 // 1st argument: take unique search range
8819 ScMatrixRef pMatSource = nullptr;
8820 SCSIZE nsC = 0, nsR = 0;
8821 switch (GetStackType())
8823 case svSingleRef:
8824 case svDoubleRef:
8825 case svMatrix:
8826 case svExternalSingleRef:
8827 case svExternalDoubleRef:
8829 pMatSource = GetMatrix();
8830 if (!pMatSource)
8832 PushIllegalParameter();
8833 return;
8836 pMatSource->GetDimensions(nsC, nsR);
8838 break;
8840 default:
8841 PushIllegalParameter();
8842 return;
8845 if (nGlobalError != FormulaError::NONE || nsC < 1 || nsR < 1)
8847 PushIllegalArgument();
8848 return;
8851 // Create unique dataset
8852 std::unordered_set<OUString> aStrSet;
8853 std::vector<std::pair<SCSIZE, OUString>> aResPos;
8854 SCSIZE nOut = bByRow ? nsR : nsC;
8855 SCSIZE nIn = bByRow ? nsC : nsR;
8857 for (SCSIZE i = 0; i < nOut; i++)
8859 OUString aStr;
8860 for (SCSIZE j = 0; j < nIn; j++)
8862 OUString aCellStr = bByRow ? pMatSource->GetString(mrContext, j, i).getString() :
8863 pMatSource->GetString(mrContext, i, j).getString();
8864 aStr += aCellStr + u"\x0001";
8867 if (aStrSet.insert(aStr).second) // unique if inserted
8869 aResPos.emplace_back(std::make_pair(i, aStr));
8871 else
8873 if (bExactly_once)
8875 auto it = std::find_if(aResPos.begin(), aResPos.end(),
8876 [&aStr](const std::pair<SCSIZE, OUString>& aRes)
8878 return aRes.second.equals(aStr);
8881 if (it != aResPos.end())
8882 aResPos.erase(it);
8886 // No result
8887 if (aResPos.size() == 0)
8889 if (nGlobalError != FormulaError::NONE)
8891 PushIllegalArgument();
8893 else
8895 PushNA();
8897 return;
8899 // fill result matrix with unique values
8900 ScMatrixRef pResMat = bByRow ? GetNewMat(nsC, aResPos.size(), /*bEmpty*/true) :
8901 GetNewMat(aResPos.size(), nsR, /*bEmpty*/true);
8902 for (SCSIZE iPos = 0; iPos < aResPos.size(); iPos++)
8904 if (bByRow)
8906 for (SCSIZE col = 0; col < nsC; col++)
8908 if (pMatSource->IsEmptyCell(col, aResPos[iPos].first))
8910 pResMat->PutEmpty(col, iPos);
8912 else if (!pMatSource->IsStringOrEmpty(col, aResPos[iPos].first))
8914 pResMat->PutDouble(pMatSource->GetDouble(col, aResPos[iPos].first), col, iPos);
8916 else
8918 pResMat->PutString(pMatSource->GetString(col, aResPos[iPos].first), col, iPos);
8922 else
8924 for (SCSIZE row = 0; row < nsR; row++)
8926 if (pMatSource->IsEmptyCell(aResPos[iPos].first, row))
8928 pResMat->PutEmpty(iPos, row);
8930 else if (!pMatSource->IsStringOrEmpty(aResPos[iPos].first, row))
8932 pResMat->PutDouble(pMatSource->GetDouble(aResPos[iPos].first, row), iPos, row);
8934 else
8936 pResMat->PutString(pMatSource->GetString(aResPos[iPos].first, row), iPos, row);
8942 if (!pResMat)
8944 PushIllegalArgument();
8946 else
8948 PushMatrix(pResMat);
8952 void ScInterpreter::getTokensAtParameter( std::unique_ptr<ScTokenArray>& pTokens, short nPos )
8954 sal_uInt16 nOpen = 0;
8955 sal_uInt16 nSepCount = 0;
8956 formula::FormulaTokenArrayPlainIterator aIter(*pArr);
8957 formula::FormulaToken* t = aIter.First();
8958 for (t = aIter.NextNoSpaces(); t; t = aIter.NextNoSpaces())
8960 OpCode aOpCode = t->GetOpCode();
8961 formula::StackVar aIntType = t->GetType();
8962 if ((aOpCode == ocOpen || aOpCode == ocArrayOpen || aOpCode == ocTableRefOpen) && aIntType == formula::StackVar::svSep)
8963 nOpen++;
8964 else if ((aOpCode == ocClose || aOpCode == ocArrayClose || aOpCode == ocTableRefClose) && aIntType == formula::StackVar::svSep)
8965 nOpen--;
8966 else if (aOpCode == ocSep && aIntType == formula::StackVar::svSep && nOpen == 1)
8968 nSepCount++;
8969 continue;
8972 if (nSepCount == nPos && nOpen > 0)
8974 pTokens->AddToken(*t->Clone());
8979 void ScInterpreter::replaceNamesToResult(const std::unordered_map<OUString, formula::FormulaToken*>& rResultIndexes,
8980 std::unique_ptr<ScTokenArray>& pTokens )
8982 formula::FormulaTokenArrayPlainIterator aIterResult(*pTokens);
8983 for (FormulaToken* t = aIterResult.GetNextStringName(); t; t = aIterResult.GetNextStringName())
8985 auto iRes = rResultIndexes.find(t->GetString().getString());
8986 if (iRes != rResultIndexes.end())
8987 pTokens->ReplaceToken(aIterResult.GetIndex() - 1, iRes->second->Clone(),
8988 FormulaTokenArray::ReplaceMode::CODE_ONLY);
8992 void ScInterpreter::ScLet()
8994 const short* pJump = pCur->GetJump();
8995 short nJumpCount = pJump[0];
8996 short nOrgJumpCount = nJumpCount;
8998 if (nJumpCount < 3 || (nJumpCount % 2 != 1))
9000 PushError(FormulaError::ParameterExpected);
9001 aCode.Jump(pJump[nOrgJumpCount], pJump[nOrgJumpCount]);
9002 return;
9005 OUString aStrName;
9006 std::unordered_map<OUString, formula::FormulaToken*> nResultIndexes;
9007 formula::FormulaTokenArrayPlainIterator aIter(*pArr);
9008 unique_ptr<ScTokenArray> pValueTokens(new ScTokenArray(mrDoc));
9010 // name and function pairs parameter
9011 while (nJumpCount > 1)
9013 if (nJumpCount == nOrgJumpCount)
9015 aStrName = GetString().getString();
9017 else if ((nOrgJumpCount - nJumpCount + 1) % 2 == 1)
9019 aIter.Jump(pJump[static_cast<short>(nOrgJumpCount - nJumpCount + 1)] - 1);
9020 FormulaToken* t = aIter.NextRPN();
9021 aStrName = t->GetString().getString();
9023 else
9025 PushError(FormulaError::ParameterExpected);
9026 aCode.Jump(pJump[nOrgJumpCount], pJump[nOrgJumpCount]);
9027 return;
9029 nJumpCount--;
9031 // get value tokens
9032 getTokensAtParameter(pValueTokens, nOrgJumpCount - nJumpCount);
9033 nJumpCount--;
9035 // replace names with result tokens
9036 replaceNamesToResult(nResultIndexes, pValueTokens);
9038 // calculate the inner results unless we already have a push result token
9039 if (pValueTokens->GetLen() == 1 && pValueTokens->GetArray()[0]->GetOpCode() == ocPush)
9041 if (!nResultIndexes.insert(std::make_pair(aStrName, pValueTokens->GetArray()[0]->Clone())).second)
9043 PushIllegalParameter();
9044 aCode.Jump(pJump[nOrgJumpCount], pJump[nOrgJumpCount]);
9045 return;
9048 else
9050 ScCompiler aComp(mrDoc, aPos, *pValueTokens, mrDoc.GetGrammar(), false, false, &mrContext);
9051 aComp.CompileTokenArray();
9052 ScInterpreter aInt(mrDoc.GetFormulaCell(aPos), mrDoc, mrContext, aPos, *pValueTokens);
9053 sfx2::LinkManager aNewLinkMgr(mrDoc.GetDocumentShell());
9054 aInt.SetLinkManager(&aNewLinkMgr);
9055 formula::StackVar aIntType = aInt.Interpret();
9057 if (aIntType == formula::svMatrixCell)
9059 ScConstMatrixRef xMat(aInt.GetResultToken()->GetMatrix());
9060 if (!nResultIndexes.insert(std::make_pair(aStrName, new ScMatrixToken(xMat->Clone()))).second)
9062 PushIllegalParameter();
9063 aCode.Jump(pJump[nOrgJumpCount], pJump[nOrgJumpCount]);
9064 return;
9067 else
9069 const FormulaConstTokenRef& xTok(aInt.GetResultToken());
9070 if (!nResultIndexes.insert(std::make_pair(aStrName, xTok->Clone())).second)
9072 PushIllegalParameter();
9073 aCode.Jump(pJump[nOrgJumpCount], pJump[nOrgJumpCount]);
9074 return;
9078 pValueTokens->Clear();
9081 // last parameter: calculation
9082 getTokensAtParameter(pValueTokens, nOrgJumpCount - nJumpCount);
9083 nJumpCount--;
9085 // replace names with result tokens
9086 replaceNamesToResult(nResultIndexes, pValueTokens);
9088 // calculate the final result
9089 ScCompiler aComp(mrDoc, aPos, *pValueTokens, mrDoc.GetGrammar(), false, false, &mrContext);
9090 aComp.CompileTokenArray();
9091 ScInterpreter aInt(mrDoc.GetFormulaCell(aPos), mrDoc, mrContext, aPos, *pValueTokens);
9092 sfx2::LinkManager aNewLinkMgr(mrDoc.GetDocumentShell());
9093 aInt.SetLinkManager(&aNewLinkMgr);
9094 formula::StackVar aIntType = aInt.Interpret();
9096 if (aIntType == formula::svMatrixCell)
9098 ScConstMatrixRef xMat(aInt.GetResultToken()->GetMatrix());
9099 PushTokenRef(new ScMatrixToken(xMat->Clone()));
9101 else
9103 const formula::FormulaConstTokenRef& xLambdaResult(aInt.GetResultToken());
9104 if (xLambdaResult)
9106 nGlobalError = xLambdaResult->GetError();
9107 if (nGlobalError == FormulaError::NONE)
9108 PushTokenRef(xLambdaResult);
9109 else
9110 PushError(nGlobalError);
9114 pValueTokens.reset();
9115 aCode.Jump(pJump[nOrgJumpCount], pJump[nOrgJumpCount]);
9118 void ScInterpreter::ScSubTotal()
9120 sal_uInt8 nParamCount = GetByte();
9121 if ( !MustHaveParamCountMinWithStackCheck( nParamCount, 2 ) )
9122 return;
9124 // We must fish the 1st parameter deep from the stack! And push it on top.
9125 const FormulaToken* p = pStack[ sp - nParamCount ];
9126 PushWithoutError( *p );
9127 sal_Int32 nFunc = GetInt32();
9128 mnSubTotalFlags |= SubtotalFlags::IgnoreNestedStAg | SubtotalFlags::IgnoreFiltered;
9129 if (nFunc > 100)
9131 // For opcodes 101 through 111, we need to skip hidden cells.
9132 // Other than that these opcodes are identical to 1 through 11.
9133 mnSubTotalFlags |= SubtotalFlags::IgnoreHidden;
9134 nFunc -= 100;
9137 if ( nGlobalError != FormulaError::NONE || nFunc < 1 || nFunc > 11 )
9138 PushIllegalArgument(); // simulate return on stack, not SetError(...)
9139 else
9141 cPar = nParamCount - 1;
9142 switch( nFunc )
9144 case SUBTOTAL_FUNC_AVE : ScAverage(); break;
9145 case SUBTOTAL_FUNC_CNT : ScCount(); break;
9146 case SUBTOTAL_FUNC_CNT2 : ScCount2(); break;
9147 case SUBTOTAL_FUNC_MAX : ScMax(); break;
9148 case SUBTOTAL_FUNC_MIN : ScMin(); break;
9149 case SUBTOTAL_FUNC_PROD : ScProduct(); break;
9150 case SUBTOTAL_FUNC_STD : ScStDev(); break;
9151 case SUBTOTAL_FUNC_STDP : ScStDevP(); break;
9152 case SUBTOTAL_FUNC_SUM : ScSum(); break;
9153 case SUBTOTAL_FUNC_VAR : ScVar(); break;
9154 case SUBTOTAL_FUNC_VARP : ScVarP(); break;
9155 default : PushIllegalArgument(); break;
9158 mnSubTotalFlags = SubtotalFlags::NONE;
9159 // Get rid of the 1st (fished) parameter.
9160 FormulaConstTokenRef xRef( PopToken());
9161 Pop();
9162 PushTokenRef( xRef);
9165 void ScInterpreter::ScAggregate()
9167 sal_uInt8 nParamCount = GetByte();
9168 if ( !MustHaveParamCountMinWithStackCheck( nParamCount, 3 ) )
9169 return;
9171 const FormulaError nErr = nGlobalError;
9172 nGlobalError = FormulaError::NONE;
9174 // fish the 1st parameter from the stack and push it on top.
9175 const FormulaToken* p = pStack[ sp - nParamCount ];
9176 PushWithoutError( *p );
9177 sal_Int32 nFunc = GetInt32();
9178 // fish the 2nd parameter from the stack and push it on top.
9179 const FormulaToken* p2 = pStack[ sp - ( nParamCount - 1 ) ];
9180 PushWithoutError( *p2 );
9181 sal_Int32 nOption = GetInt32();
9183 if ( nGlobalError != FormulaError::NONE || nFunc < 1 || nFunc > 19 )
9185 nGlobalError = nErr;
9186 PushIllegalArgument();
9188 else
9190 switch ( nOption)
9192 case 0 : // ignore nested SUBTOTAL and AGGREGATE functions
9193 mnSubTotalFlags = SubtotalFlags::IgnoreNestedStAg;
9194 break;
9195 case 1 : // ignore hidden rows, nested SUBTOTAL and AGGREGATE functions
9196 mnSubTotalFlags = SubtotalFlags::IgnoreHidden | SubtotalFlags::IgnoreNestedStAg;
9197 break;
9198 case 2 : // ignore error values, nested SUBTOTAL and AGGREGATE functions
9199 mnSubTotalFlags = SubtotalFlags::IgnoreErrVal | SubtotalFlags::IgnoreNestedStAg;
9200 break;
9201 case 3 : // ignore hidden rows, error values, nested SUBTOTAL and AGGREGATE functions
9202 mnSubTotalFlags = SubtotalFlags::IgnoreHidden | SubtotalFlags::IgnoreErrVal | SubtotalFlags::IgnoreNestedStAg;
9203 break;
9204 case 4 : // ignore nothing
9205 mnSubTotalFlags = SubtotalFlags::NONE;
9206 break;
9207 case 5 : // ignore hidden rows
9208 mnSubTotalFlags = SubtotalFlags::IgnoreHidden ;
9209 break;
9210 case 6 : // ignore error values
9211 mnSubTotalFlags = SubtotalFlags::IgnoreErrVal ;
9212 break;
9213 case 7 : // ignore hidden rows and error values
9214 mnSubTotalFlags = SubtotalFlags::IgnoreHidden | SubtotalFlags::IgnoreErrVal ;
9215 break;
9216 default :
9217 nGlobalError = nErr;
9218 PushIllegalArgument();
9219 return;
9222 if ((mnSubTotalFlags & SubtotalFlags::IgnoreErrVal) == SubtotalFlags::NONE)
9223 nGlobalError = nErr;
9225 cPar = nParamCount - 2;
9226 switch ( nFunc )
9228 case AGGREGATE_FUNC_AVE : ScAverage(); break;
9229 case AGGREGATE_FUNC_CNT : ScCount(); break;
9230 case AGGREGATE_FUNC_CNT2 : ScCount2(); break;
9231 case AGGREGATE_FUNC_MAX : ScMax(); break;
9232 case AGGREGATE_FUNC_MIN : ScMin(); break;
9233 case AGGREGATE_FUNC_PROD : ScProduct(); break;
9234 case AGGREGATE_FUNC_STD : ScStDev(); break;
9235 case AGGREGATE_FUNC_STDP : ScStDevP(); break;
9236 case AGGREGATE_FUNC_SUM : ScSum(); break;
9237 case AGGREGATE_FUNC_VAR : ScVar(); break;
9238 case AGGREGATE_FUNC_VARP : ScVarP(); break;
9239 case AGGREGATE_FUNC_MEDIAN : ScMedian(); break;
9240 case AGGREGATE_FUNC_MODSNGL : ScModalValue(); break;
9241 case AGGREGATE_FUNC_LARGE : ScLarge(); break;
9242 case AGGREGATE_FUNC_SMALL : ScSmall(); break;
9243 case AGGREGATE_FUNC_PERCINC : ScPercentile( true ); break;
9244 case AGGREGATE_FUNC_QRTINC : ScQuartile( true ); break;
9245 case AGGREGATE_FUNC_PERCEXC : ScPercentile( false ); break;
9246 case AGGREGATE_FUNC_QRTEXC : ScQuartile( false ); break;
9247 default:
9248 nGlobalError = nErr;
9249 PushIllegalArgument();
9250 break;
9252 mnSubTotalFlags = SubtotalFlags::NONE;
9254 FormulaConstTokenRef xRef( PopToken());
9255 // Get rid of the 1st and 2nd (fished) parameters.
9256 Pop();
9257 Pop();
9258 PushTokenRef( xRef);
9261 std::unique_ptr<ScDBQueryParamBase> ScInterpreter::GetDBParams( bool& rMissingField )
9263 bool bAllowMissingField = false;
9264 if ( rMissingField )
9266 bAllowMissingField = true;
9267 rMissingField = false;
9269 if ( GetByte() == 3 )
9271 // First, get the query criteria range.
9272 ::std::unique_ptr<ScDBRangeBase> pQueryRef( PopDBDoubleRef() );
9273 if (!pQueryRef)
9274 return nullptr;
9276 bool bByVal = true;
9277 double nVal = 0.0;
9278 svl::SharedString aStr;
9279 ScRange aMissingRange;
9280 bool bRangeFake = false;
9281 switch (GetStackType())
9283 case svDouble :
9284 nVal = ::rtl::math::approxFloor( GetDouble() );
9285 if ( bAllowMissingField && nVal == 0.0 )
9286 rMissingField = true; // fake missing parameter
9287 break;
9288 case svString :
9289 bByVal = false;
9290 aStr = GetString();
9291 break;
9292 case svSingleRef :
9294 ScAddress aAdr;
9295 PopSingleRef( aAdr );
9296 ScRefCellValue aCell(mrDoc, aAdr);
9297 if (aCell.hasNumeric())
9298 nVal = GetCellValue(aAdr, aCell);
9299 else
9301 bByVal = false;
9302 GetCellString(aStr, aCell);
9305 break;
9306 case svDoubleRef :
9307 if ( bAllowMissingField )
9308 { // fake missing parameter for old SO compatibility
9309 bRangeFake = true;
9310 PopDoubleRef( aMissingRange );
9312 else
9314 PopError();
9315 SetError( FormulaError::IllegalParameter );
9317 break;
9318 case svMissing :
9319 PopError();
9320 if ( bAllowMissingField )
9321 rMissingField = true;
9322 else
9323 SetError( FormulaError::IllegalParameter );
9324 break;
9325 default:
9326 PopError();
9327 SetError( FormulaError::IllegalParameter );
9330 if (nGlobalError != FormulaError::NONE)
9331 return nullptr;
9333 unique_ptr<ScDBRangeBase> pDBRef( PopDBDoubleRef() );
9335 if (nGlobalError != FormulaError::NONE || !pDBRef)
9336 return nullptr;
9338 if ( bRangeFake )
9340 // range parameter must match entire database range
9341 if (pDBRef->isRangeEqual(aMissingRange))
9342 rMissingField = true;
9343 else
9344 SetError( FormulaError::IllegalParameter );
9347 if (nGlobalError != FormulaError::NONE)
9348 return nullptr;
9350 SCCOL nField = pDBRef->getFirstFieldColumn();
9351 if (rMissingField)
9352 ; // special case
9353 else if (bByVal)
9354 nField = pDBRef->findFieldColumn(static_cast<SCCOL>(nVal));
9355 else
9357 FormulaError nErr = FormulaError::NONE;
9358 nField = pDBRef->findFieldColumn(aStr.getString(), &nErr);
9359 SetError(nErr);
9362 if (!mrDoc.ValidCol(nField))
9363 return nullptr;
9365 unique_ptr<ScDBQueryParamBase> pParam( pDBRef->createQueryParam(pQueryRef.get()) );
9367 if (pParam)
9369 // An allowed missing field parameter sets the result field
9370 // to any of the query fields, just to be able to return
9371 // some cell from the iterator.
9372 if ( rMissingField )
9373 nField = static_cast<SCCOL>(pParam->GetEntry(0).nField);
9374 pParam->mnField = nField;
9376 SCSIZE nCount = pParam->GetEntryCount();
9377 for ( SCSIZE i=0; i < nCount; i++ )
9379 ScQueryEntry& rEntry = pParam->GetEntry(i);
9380 if (!rEntry.bDoQuery)
9381 break;
9383 ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
9384 sal_uInt32 nIndex = 0;
9385 OUString aQueryStr = rItem.maString.getString();
9386 bool bNumber = mrContext.NFIsNumberFormat(
9387 aQueryStr, nIndex, rItem.mfVal);
9388 rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString;
9390 if (!bNumber && pParam->eSearchType == utl::SearchParam::SearchType::Normal)
9391 pParam->eSearchType = DetectSearchType(aQueryStr, mrDoc);
9393 return pParam;
9396 return nullptr;
9399 void ScInterpreter::DBIterator( ScIterFunc eFunc )
9401 double fRes = 0;
9402 KahanSum fErg = 0;
9403 sal_uLong nCount = 0;
9404 bool bMissingField = false;
9405 unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) );
9406 if (pQueryParam)
9408 if (!pQueryParam->IsValidFieldIndex())
9410 SetError(FormulaError::NoValue);
9411 return;
9413 ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam));
9414 ScDBQueryDataIterator::Value aValue;
9415 if ( aValIter.GetFirst(aValue) && aValue.mnError == FormulaError::NONE )
9417 switch( eFunc )
9419 case ifPRODUCT: fRes = 1; break;
9420 case ifMAX: fRes = -MAXDOUBLE; break;
9421 case ifMIN: fRes = MAXDOUBLE; break;
9422 default: ; // nothing
9427 nCount++;
9428 switch( eFunc )
9430 case ifAVERAGE:
9431 case ifSUM:
9432 fErg += aValue.mfValue;
9433 break;
9434 case ifSUMSQ:
9435 fErg += aValue.mfValue * aValue.mfValue;
9436 break;
9437 case ifPRODUCT:
9438 fRes *= aValue.mfValue;
9439 break;
9440 case ifMAX:
9441 if( aValue.mfValue > fRes ) fRes = aValue.mfValue;
9442 break;
9443 case ifMIN:
9444 if( aValue.mfValue < fRes ) fRes = aValue.mfValue;
9445 break;
9446 default: ; // nothing
9449 while ( aValIter.GetNext(aValue) && aValue.mnError == FormulaError::NONE );
9451 SetError(aValue.mnError);
9453 else
9454 SetError( FormulaError::IllegalParameter);
9455 switch( eFunc )
9457 case ifCOUNT: fRes = nCount; break;
9458 case ifSUM: fRes = fErg.get(); break;
9459 case ifSUMSQ: fRes = fErg.get(); break;
9460 case ifAVERAGE: fRes = div(fErg.get(), nCount); break;
9461 default: ; // nothing
9463 PushDouble( fRes );
9466 void ScInterpreter::ScDBSum()
9468 DBIterator( ifSUM );
9471 void ScInterpreter::ScDBCount()
9473 bool bMissingField = true;
9474 unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) );
9475 if (pQueryParam)
9477 sal_uLong nCount = 0;
9478 if ( bMissingField && pQueryParam->GetType() == ScDBQueryParamBase::INTERNAL )
9479 { // count all matching records
9480 // TODO: currently the QueryIterators only return cell pointers of
9481 // existing cells, so if a query matches an empty cell there's
9482 // nothing returned, and therefore not counted!
9483 // Since this has ever been the case and this code here only came
9484 // into existence to fix #i6899 and it never worked before we'll
9485 // have to live with it until we reimplement the iterators to also
9486 // return empty cells, which would mean to adapt all callers of
9487 // iterators.
9488 ScDBQueryParamInternal* p = static_cast<ScDBQueryParamInternal*>(pQueryParam.get());
9489 p->nCol2 = p->nCol1; // Don't forget to select only one column.
9490 SCTAB nTab = p->nTab;
9491 // ScQueryCellIteratorDirect doesn't make use of ScDBQueryParamBase::mnField,
9492 // so the source range has to be restricted, like before the introduction
9493 // of ScDBQueryParamBase.
9494 p->nCol1 = p->nCol2 = p->mnField;
9495 ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab, *p, true, false);
9496 if ( aCellIter.GetFirst() )
9500 nCount++;
9501 } while ( aCellIter.GetNext() );
9504 else
9505 { // count only matching records with a value in the "result" field
9506 if (!pQueryParam->IsValidFieldIndex())
9508 SetError(FormulaError::NoValue);
9509 return;
9511 ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam));
9512 ScDBQueryDataIterator::Value aValue;
9513 if ( aValIter.GetFirst(aValue) && aValue.mnError == FormulaError::NONE )
9517 nCount++;
9519 while ( aValIter.GetNext(aValue) && aValue.mnError == FormulaError::NONE );
9521 SetError(aValue.mnError);
9523 PushDouble( nCount );
9525 else
9526 PushIllegalParameter();
9529 void ScInterpreter::ScDBCount2()
9531 bool bMissingField = true;
9532 unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) );
9533 if (pQueryParam)
9535 if (!pQueryParam->IsValidFieldIndex())
9537 SetError(FormulaError::NoValue);
9538 return;
9540 sal_uLong nCount = 0;
9541 pQueryParam->mbSkipString = false;
9542 ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam));
9543 ScDBQueryDataIterator::Value aValue;
9544 if ( aValIter.GetFirst(aValue) && aValue.mnError == FormulaError::NONE )
9548 nCount++;
9550 while ( aValIter.GetNext(aValue) && aValue.mnError == FormulaError::NONE );
9552 SetError(aValue.mnError);
9553 PushDouble( nCount );
9555 else
9556 PushIllegalParameter();
9559 void ScInterpreter::ScDBAverage()
9561 DBIterator( ifAVERAGE );
9564 void ScInterpreter::ScDBMax()
9566 DBIterator( ifMAX );
9569 void ScInterpreter::ScDBMin()
9571 DBIterator( ifMIN );
9574 void ScInterpreter::ScDBProduct()
9576 DBIterator( ifPRODUCT );
9579 void ScInterpreter::GetDBStVarParams( double& rVal, double& rValCount )
9581 std::vector<double> values;
9582 KahanSum vSum = 0.0;
9583 KahanSum fSum = 0.0;
9585 rValCount = 0.0;
9586 bool bMissingField = false;
9587 unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) );
9588 if (pQueryParam)
9590 if (!pQueryParam->IsValidFieldIndex())
9592 SetError(FormulaError::NoValue);
9593 return;
9595 ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam));
9596 ScDBQueryDataIterator::Value aValue;
9597 if (aValIter.GetFirst(aValue) && aValue.mnError == FormulaError::NONE)
9601 rValCount++;
9602 values.push_back(aValue.mfValue);
9603 fSum += aValue.mfValue;
9605 while ((aValue.mnError == FormulaError::NONE) && aValIter.GetNext(aValue));
9607 SetError(aValue.mnError);
9609 else
9610 SetError( FormulaError::IllegalParameter);
9612 double vMean = fSum.get() / values.size();
9614 for (double v : values)
9615 vSum += (v - vMean) * (v - vMean);
9617 rVal = vSum.get();
9620 void ScInterpreter::ScDBStdDev()
9622 double fVal, fCount;
9623 GetDBStVarParams( fVal, fCount );
9624 PushDouble( sqrt(fVal/(fCount-1)));
9627 void ScInterpreter::ScDBStdDevP()
9629 double fVal, fCount;
9630 GetDBStVarParams( fVal, fCount );
9631 PushDouble( sqrt(fVal/fCount));
9634 void ScInterpreter::ScDBVar()
9636 double fVal, fCount;
9637 GetDBStVarParams( fVal, fCount );
9638 PushDouble(fVal/(fCount-1));
9641 void ScInterpreter::ScDBVarP()
9643 double fVal, fCount;
9644 GetDBStVarParams( fVal, fCount );
9645 PushDouble(fVal/fCount);
9648 static bool lcl_IsTableStructuredRef(const OUString& sRefStr, sal_Int32& nIndex)
9650 nIndex = ScGlobal::FindUnquoted(sRefStr, '[');
9651 return (nIndex > 0 && ScGlobal::FindUnquoted(sRefStr, ']', nIndex + 1) > nIndex);
9654 void ScInterpreter::ScIndirect()
9656 sal_uInt8 nParamCount = GetByte();
9657 if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
9658 return;
9660 // Reference address syntax for INDIRECT is configurable.
9661 FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax;
9662 if (eConv == FormulaGrammar::CONV_UNSPECIFIED)
9663 // Use the current address syntax if unspecified.
9664 eConv = mrDoc.GetAddressConvention();
9666 // either CONV_A1_XL_A1 was explicitly configured, or it wasn't possible
9667 // to determine which syntax to use during doc import
9668 bool bTryXlA1 = (eConv == FormulaGrammar::CONV_A1_XL_A1);
9670 if (nParamCount == 2 && 0.0 == GetDouble() )
9672 // Overwrite the config and try Excel R1C1.
9673 eConv = FormulaGrammar::CONV_XL_R1C1;
9674 bTryXlA1 = false;
9677 svl::SharedString sSharedRefStr = GetString();
9678 const OUString & sRefStr = sSharedRefStr.getString();
9679 if (sRefStr.isEmpty())
9681 // Bail out early for empty cells, rely on "we do have a string" below.
9682 PushError( FormulaError::NoRef);
9683 return;
9686 const ScAddress::Details aDetails( bTryXlA1 ? FormulaGrammar::CONV_OOO : eConv, aPos );
9687 const ScAddress::Details aDetailsXlA1( FormulaGrammar::CONV_XL_A1, aPos );
9688 SCTAB nTab = aPos.Tab();
9690 bool bTableRefNamed = false;
9691 sal_Int32 nTableRefNamedIndex = -1;
9692 OUString sTabRefStr;
9694 // Named expressions and DB range names need to be tried first, as older 1K
9695 // columns allowed names that would now match a 16k columns cell address.
9698 ScRangeData* pData = ScRangeStringConverter::GetRangeDataFromString( sRefStr, nTab, mrDoc, eConv);
9699 if (!pData)
9700 break;
9702 // We need this in order to obtain a good range.
9703 pData->ValidateTabRefs();
9705 ScRange aRange;
9707 // This is the usual way to treat named ranges containing
9708 // relative references.
9709 if (!pData->IsReference(aRange, aPos))
9711 sTabRefStr = pData->GetSymbol();
9712 bTableRefNamed = lcl_IsTableStructuredRef(sTabRefStr, nTableRefNamedIndex);
9713 // if bTableRefNamed is true, we have a name that maps to a table structured reference.
9714 // Such a case is handled below.
9715 break;
9718 if (aRange.aStart == aRange.aEnd)
9719 PushSingleRef( aRange.aStart.Col(), aRange.aStart.Row(),
9720 aRange.aStart.Tab());
9721 else
9722 PushDoubleRef( aRange.aStart.Col(), aRange.aStart.Row(),
9723 aRange.aStart.Tab(), aRange.aEnd.Col(),
9724 aRange.aEnd.Row(), aRange.aEnd.Tab());
9726 // success!
9727 return;
9729 while (false);
9733 if (bTableRefNamed)
9734 break;
9736 const OUString & aName( sSharedRefStr.getIgnoreCaseString() );
9737 ScDBCollection::NamedDBs& rDBs = mrDoc.GetDBCollection()->getNamedDBs();
9738 const ScDBData* pData = rDBs.findByUpperName( aName);
9739 if (!pData)
9740 break;
9742 ScRange aRange;
9743 pData->GetArea( aRange);
9745 // In Excel, specifying a table name without [] resolves to the
9746 // same as with [], a range that excludes header and totals
9747 // rows and contains only data rows. Do the same.
9748 if (pData->HasHeader())
9749 aRange.aStart.IncRow();
9750 if (pData->HasTotals())
9751 aRange.aEnd.IncRow(-1);
9753 if (aRange.aStart.Row() > aRange.aEnd.Row())
9754 break;
9756 if (aRange.aStart == aRange.aEnd)
9757 PushSingleRef( aRange.aStart.Col(), aRange.aStart.Row(),
9758 aRange.aStart.Tab());
9759 else
9760 PushDoubleRef( aRange.aStart.Col(), aRange.aStart.Row(),
9761 aRange.aStart.Tab(), aRange.aEnd.Col(),
9762 aRange.aEnd.Row(), aRange.aEnd.Tab());
9764 // success!
9765 return;
9767 while (false);
9769 ScRefAddress aRefAd, aRefAd2;
9770 ScAddress::ExternalInfo aExtInfo;
9771 if ( !bTableRefNamed &&
9772 (ConvertDoubleRef(mrDoc, sRefStr, nTab, aRefAd, aRefAd2, aDetails, &aExtInfo) ||
9773 ( bTryXlA1 && ConvertDoubleRef(mrDoc, sRefStr, nTab, aRefAd,
9774 aRefAd2, aDetailsXlA1, &aExtInfo) ) ) )
9776 if (aExtInfo.mbExternal)
9778 PushExternalDoubleRef(
9779 aExtInfo.mnFileId, aExtInfo.maTabName,
9780 aRefAd.Col(), aRefAd.Row(), aRefAd.Tab(),
9781 aRefAd2.Col(), aRefAd2.Row(), aRefAd2.Tab());
9783 else
9784 PushDoubleRef( aRefAd, aRefAd2);
9786 else if ( !bTableRefNamed &&
9787 (ConvertSingleRef(mrDoc, sRefStr, nTab, aRefAd, aDetails, &aExtInfo) ||
9788 ( bTryXlA1 && ConvertSingleRef (mrDoc, sRefStr, nTab, aRefAd,
9789 aDetailsXlA1, &aExtInfo) ) ) )
9791 if (aExtInfo.mbExternal)
9793 PushExternalSingleRef(
9794 aExtInfo.mnFileId, aExtInfo.maTabName, aRefAd.Col(), aRefAd.Row(), aRefAd.Tab());
9796 else
9797 PushSingleRef( aRefAd);
9799 else
9801 // It may be even a TableRef or an external name.
9802 // Anything else that resolves to one reference could be added
9803 // here, but we don't want to compile every arbitrary string. This
9804 // is already nasty enough...
9805 sal_Int32 nIndex = bTableRefNamed ? nTableRefNamedIndex : -1;
9806 bool bTableRef = bTableRefNamed;
9807 if (!bTableRefNamed)
9808 bTableRef = lcl_IsTableStructuredRef(sRefStr, nIndex);
9809 bool bExternalName = false; // External references would had been consumed above already.
9810 if (!bTableRef)
9812 // This is our own file name reference representation centric.. but
9813 // would work also for XL '[doc]'!name and also for
9814 // '[doc]Sheet1'!name ... sickos.
9815 if (sRefStr[0] == '\'')
9817 // Minimum 'a'#name or 'a'!name
9818 // bTryXlA1 means try both, first our own.
9819 if (bTryXlA1 || eConv == FormulaGrammar::CONV_OOO)
9821 nIndex = ScGlobal::FindUnquoted( sRefStr, '#');
9822 if (nIndex >= 3 && sRefStr[nIndex-1] == '\'')
9824 bExternalName = true;
9825 eConv = FormulaGrammar::CONV_OOO;
9828 if (!bExternalName && (bTryXlA1 || eConv != FormulaGrammar::CONV_OOO))
9830 nIndex = ScGlobal::FindUnquoted( sRefStr, '!');
9831 if (nIndex >= 3 && sRefStr[nIndex-1] == '\'')
9833 bExternalName = true;
9839 if (bExternalName || bTableRef)
9843 ScCompiler aComp( mrDoc, aPos, mrDoc.GetGrammar());
9844 aComp.SetRefConvention( eConv); // must be after grammar
9845 std::unique_ptr<ScTokenArray> pTokArr( aComp.CompileString(bTableRefNamed ? sTabRefStr : sRefStr));
9847 if (pTokArr->GetCodeError() != FormulaError::NONE || !pTokArr->GetLen())
9848 break;
9850 // Whatever... use only the specific case.
9851 if (bExternalName)
9853 const formula::FormulaToken* pTok = pTokArr->FirstToken();
9854 if (!pTok || pTok->GetType() != svExternalName)
9855 break;
9857 else if (!pTokArr->HasOpCode( ocTableRef))
9858 break;
9860 aComp.CompileTokenArray();
9862 // A syntactically valid reference will generate exactly
9863 // one RPN token, a reference or error. Discard everything
9864 // else as error.
9865 if (pTokArr->GetCodeLen() != 1)
9866 break;
9868 ScTokenRef xTok( pTokArr->FirstRPNToken());
9869 if (!xTok)
9870 break;
9872 switch (xTok->GetType())
9874 case svSingleRef:
9875 case svDoubleRef:
9876 case svExternalSingleRef:
9877 case svExternalDoubleRef:
9878 case svError:
9879 PushTokenRef( xTok);
9880 // success!
9881 return;
9882 default:
9883 ; // nothing
9886 while (false);
9889 PushError( FormulaError::NoRef);
9893 void ScInterpreter::ScAddressFunc()
9895 OUString sTabStr;
9897 sal_uInt8 nParamCount = GetByte();
9898 if( !MustHaveParamCount( nParamCount, 2, 5 ) )
9899 return;
9901 if( nParamCount >= 5 )
9902 sTabStr = GetString().getString();
9904 FormulaGrammar::AddressConvention eConv = FormulaGrammar::CONV_OOO; // default
9905 if (nParamCount >= 4 && 0.0 == GetDoubleWithDefault( 1.0))
9906 eConv = FormulaGrammar::CONV_XL_R1C1;
9907 else
9909 // If A1 syntax is requested then the actual sheet separator and format
9910 // convention depends on the syntax configured for INDIRECT to match
9911 // that, and if it is unspecified then the document's address syntax.
9912 FormulaGrammar::AddressConvention eForceConv = maCalcConfig.meStringRefAddressSyntax;
9913 if (eForceConv == FormulaGrammar::CONV_UNSPECIFIED)
9914 eForceConv = mrDoc.GetAddressConvention();
9915 if (eForceConv == FormulaGrammar::CONV_XL_A1 || eForceConv == FormulaGrammar::CONV_XL_R1C1)
9916 eConv = FormulaGrammar::CONV_XL_A1; // for anything Excel use Excel A1
9919 ScRefFlags nFlags = ScRefFlags::COL_ABS | ScRefFlags::ROW_ABS; // default
9920 if( nParamCount >= 3 )
9922 sal_Int32 n = GetInt32WithDefault(1);
9923 switch ( n )
9925 default :
9926 PushNoValue();
9927 return;
9929 case 5:
9930 case 1 : break; // default
9931 case 6:
9932 case 2 : nFlags = ScRefFlags::ROW_ABS; break;
9933 case 7:
9934 case 3 : nFlags = ScRefFlags::COL_ABS; break;
9935 case 8:
9936 case 4 : nFlags = ScRefFlags::ZERO; break; // both relative
9939 nFlags |= ScRefFlags::VALID | ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID;
9941 SCCOL nCol = static_cast<SCCOL>(GetInt16());
9942 SCROW nRow = static_cast<SCROW>(GetInt32());
9943 if( eConv == FormulaGrammar::CONV_XL_R1C1 )
9945 // YUCK! The XL interface actually treats rel R1C1 refs differently
9946 // than A1
9947 if( !(nFlags & ScRefFlags::COL_ABS) )
9948 nCol += aPos.Col() + 1;
9949 if( !(nFlags & ScRefFlags::ROW_ABS) )
9950 nRow += aPos.Row() + 1;
9953 --nCol;
9954 --nRow;
9955 if (nGlobalError != FormulaError::NONE || !mrDoc.ValidCol( nCol) || !mrDoc.ValidRow( nRow))
9957 PushIllegalArgument();
9958 return;
9961 const ScAddress::Details aDetails( eConv, aPos );
9962 const ScAddress aAdr( nCol, nRow, 0);
9963 OUString aRefStr(aAdr.Format(nFlags, &mrDoc, aDetails));
9965 if( nParamCount >= 5 && !sTabStr.isEmpty() )
9967 OUString aDoc;
9968 if (eConv == FormulaGrammar::CONV_OOO)
9970 // Isolate Tab from 'Doc'#Tab
9971 sal_Int32 nPos = ScCompiler::GetDocTabPos( sTabStr);
9972 if (nPos != -1)
9974 if (sTabStr[nPos+1] == '$')
9975 ++nPos; // also split 'Doc'#$Tab
9976 aDoc = sTabStr.copy( 0, nPos+1);
9977 sTabStr = sTabStr.copy( nPos+1);
9980 /* TODO: yet unsupported external reference in CONV_XL_R1C1 syntax may
9981 * need some extra handling to isolate Tab from Doc. */
9982 if (sTabStr[0] != '\'' || !sTabStr.endsWith("'"))
9983 ScCompiler::CheckTabQuotes( sTabStr, eConv);
9984 if (!aDoc.isEmpty())
9985 sTabStr = aDoc + sTabStr;
9986 sTabStr += (eConv == FormulaGrammar::CONV_XL_R1C1 || eConv == FormulaGrammar::CONV_XL_A1) ?
9987 std::u16string_view(u"!") : std::u16string_view(u".");
9988 sTabStr += aRefStr;
9989 PushString( sTabStr );
9991 else
9992 PushString( aRefStr );
9995 void ScInterpreter::ScOffset()
9997 sal_uInt8 nParamCount = GetByte();
9998 if ( !MustHaveParamCount( nParamCount, 3, 5 ) )
9999 return;
10001 bool bNewWidth = false;
10002 bool bNewHeight = false;
10003 sal_Int32 nColNew = 1, nRowNew = 1;
10004 if (nParamCount == 5)
10006 if (IsMissing())
10007 PopError();
10008 else
10010 nColNew = GetInt32();
10011 bNewWidth = true;
10014 if (nParamCount >= 4)
10016 if (IsMissing())
10017 PopError();
10018 else
10020 nRowNew = GetInt32();
10021 bNewHeight = true;
10024 sal_Int32 nColPlus = GetInt32();
10025 sal_Int32 nRowPlus = GetInt32();
10026 if (nGlobalError != FormulaError::NONE)
10028 PushError( nGlobalError);
10029 return;
10031 if (nColNew <= 0 || nRowNew <= 0)
10033 PushIllegalArgument();
10034 return;
10036 SCCOL nCol1(0);
10037 SCROW nRow1(0);
10038 SCTAB nTab1(0);
10039 SCCOL nCol2(0);
10040 SCROW nRow2(0);
10041 SCTAB nTab2(0);
10042 switch (GetStackType())
10044 case svSingleRef:
10046 PopSingleRef(nCol1, nRow1, nTab1);
10047 if (!bNewWidth && !bNewHeight)
10049 nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1) + nColPlus);
10050 nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1) + nRowPlus);
10051 if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1))
10052 PushIllegalArgument();
10053 else
10054 PushSingleRef(nCol1, nRow1, nTab1);
10056 else
10058 nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColPlus);
10059 nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowPlus);
10060 nCol2 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColNew-1);
10061 nRow2 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowNew-1);
10062 if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1) ||
10063 !mrDoc.ValidCol(nCol2) || !mrDoc.ValidRow(nRow2))
10064 PushIllegalArgument();
10065 else
10066 PushDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab1);
10068 break;
10070 case svExternalSingleRef:
10072 sal_uInt16 nFileId;
10073 OUString aTabName;
10074 ScSingleRefData aRef;
10075 PopExternalSingleRef(nFileId, aTabName, aRef);
10076 ScAddress aAbsRef = aRef.toAbs(mrDoc, aPos);
10077 nCol1 = aAbsRef.Col();
10078 nRow1 = aAbsRef.Row();
10079 nTab1 = aAbsRef.Tab();
10081 if (!bNewWidth && !bNewHeight)
10083 nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1) + nColPlus);
10084 nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1) + nRowPlus);
10085 if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1))
10086 PushIllegalArgument();
10087 else
10088 PushExternalSingleRef(nFileId, aTabName, nCol1, nRow1, nTab1);
10090 else
10092 nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColPlus);
10093 nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowPlus);
10094 nCol2 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColNew-1);
10095 nRow2 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowNew-1);
10096 nTab2 = nTab1;
10097 if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1) ||
10098 !mrDoc.ValidCol(nCol2) || !mrDoc.ValidRow(nRow2))
10099 PushIllegalArgument();
10100 else
10101 PushExternalDoubleRef(nFileId, aTabName, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
10103 break;
10105 case svDoubleRef:
10107 PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
10108 if (!bNewWidth)
10109 nColNew = nCol2 - nCol1 + 1;
10110 if (!bNewHeight)
10111 nRowNew = nRow2 - nRow1 + 1;
10112 nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColPlus);
10113 nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowPlus);
10114 nCol2 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColNew-1);
10115 nRow2 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowNew-1);
10116 if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1) ||
10117 !mrDoc.ValidCol(nCol2) || !mrDoc.ValidRow(nRow2) || nTab1 != nTab2)
10118 PushIllegalArgument();
10119 else
10120 PushDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab1);
10121 break;
10123 case svExternalDoubleRef:
10125 sal_uInt16 nFileId;
10126 OUString aTabName;
10127 ScComplexRefData aRef;
10128 PopExternalDoubleRef(nFileId, aTabName, aRef);
10129 ScRange aAbs = aRef.toAbs(mrDoc, aPos);
10130 nCol1 = aAbs.aStart.Col();
10131 nRow1 = aAbs.aStart.Row();
10132 nTab1 = aAbs.aStart.Tab();
10133 nCol2 = aAbs.aEnd.Col();
10134 nRow2 = aAbs.aEnd.Row();
10135 nTab2 = aAbs.aEnd.Tab();
10136 if (!bNewWidth)
10137 nColNew = nCol2 - nCol1 + 1;
10138 if (!bNewHeight)
10139 nRowNew = nRow2 - nRow1 + 1;
10140 nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColPlus);
10141 nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowPlus);
10142 nCol2 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColNew-1);
10143 nRow2 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowNew-1);
10144 if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1) ||
10145 !mrDoc.ValidCol(nCol2) || !mrDoc.ValidRow(nRow2) || nTab1 != nTab2)
10146 PushIllegalArgument();
10147 else
10148 PushExternalDoubleRef(nFileId, aTabName, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
10149 break;
10151 default:
10152 PushIllegalParameter();
10153 break;
10154 } // end switch
10157 void ScInterpreter::ScIndex()
10159 sal_uInt8 nParamCount = GetByte();
10160 if ( !MustHaveParamCount( nParamCount, 1, 4 ) )
10161 return;
10163 sal_uInt32 nArea;
10164 size_t nAreaCount;
10165 SCCOL nCol;
10166 SCROW nRow;
10167 if (nParamCount == 4)
10168 nArea = GetUInt32();
10169 else
10170 nArea = 1;
10171 bool bColMissing;
10172 if (nParamCount >= 3)
10174 bColMissing = IsMissing();
10175 nCol = static_cast<SCCOL>(GetInt16());
10177 else
10179 bColMissing = false;
10180 nCol = 0;
10182 if (nParamCount >= 2)
10183 nRow = static_cast<SCROW>(GetInt32());
10184 else
10185 nRow = 0;
10186 if (GetStackType() == svRefList)
10187 nAreaCount = (sp ? pStack[sp-1]->GetRefList()->size() : 0);
10188 else
10189 nAreaCount = 1; // one reference or array or whatever
10190 if (nGlobalError != FormulaError::NONE || nAreaCount == 0 || static_cast<size_t>(nArea) > nAreaCount)
10192 PushError( FormulaError::NoRef);
10193 return;
10195 else if (nArea < 1 || nCol < 0 || nRow < 0)
10197 PushIllegalArgument();
10198 return;
10200 switch (GetStackType())
10202 case svMatrix:
10203 case svExternalSingleRef:
10204 case svExternalDoubleRef:
10206 if (nArea != 1)
10207 SetError(FormulaError::IllegalArgument);
10208 sal_uInt16 nOldSp = sp;
10209 ScMatrixRef pMat = GetMatrix();
10210 if (pMat)
10212 SCSIZE nC, nR;
10213 pMat->GetDimensions(nC, nR);
10215 // Access one element of a vector independent of col/row
10216 // orientation. Excel documentation does not mention, but
10217 // i62850 had a .xls example of a row vector accessed by
10218 // row number returning one element. This
10219 // INDEX(row_vector;element) behaves the same as
10220 // INDEX(row_vector;0;element) and thus contradicts Excel
10221 // documentation where the second parameter is always
10222 // row_num.
10224 // ODFF v1.3 in 6.14.6 INDEX states "If DataSource is a
10225 // one-dimensional row vector, Row is optional, which
10226 // effectively makes Row act as the column offset into the
10227 // vector". Guess the first Row is a typo and should read
10228 // Column instead.
10230 const bool bRowVectorSpecial = (nParamCount == 2 || bColMissing);
10231 const bool bRowVectorElement = (nR == 1 && (nCol != 0 || (bRowVectorSpecial && nRow != 0)));
10232 const bool bVectorElement = (bRowVectorElement || (nC == 1 && nRow != 0));
10234 if (nC == 0 || nR == 0 ||
10235 (!bVectorElement && (o3tl::make_unsigned(nCol) > nC ||
10236 o3tl::make_unsigned(nRow) > nR)))
10237 PushError( FormulaError::NoRef);
10238 else if (nCol == 0 && nRow == 0)
10239 sp = nOldSp;
10240 else if (bVectorElement)
10242 // Vectors here don't replicate to the other dimension.
10243 SCSIZE nElement, nOtherDimension;
10244 if (bRowVectorElement && !bRowVectorSpecial)
10246 nElement = o3tl::make_unsigned(nCol);
10247 nOtherDimension = o3tl::make_unsigned(nRow);
10249 else
10251 nElement = o3tl::make_unsigned(nRow);
10252 nOtherDimension = o3tl::make_unsigned(nCol);
10255 if (nElement == 0 || nElement > nC * nR || nOtherDimension > 1)
10256 PushError( FormulaError::NoRef);
10257 else
10259 --nElement;
10260 if (pMat->IsStringOrEmpty( nElement))
10261 PushString( pMat->GetString(nElement).getString());
10262 else
10263 PushDouble( pMat->GetDouble( nElement));
10266 else if (nCol == 0)
10268 ScMatrixRef pResMat = GetNewMat(nC, 1, /*bEmpty*/true);
10269 if (pResMat)
10271 SCSIZE nRowMinus1 = static_cast<SCSIZE>(nRow - 1);
10272 for (SCSIZE i = 0; i < nC; i++)
10273 if (!pMat->IsStringOrEmpty(i, nRowMinus1))
10274 pResMat->PutDouble(pMat->GetDouble(i,
10275 nRowMinus1), i, 0);
10276 else
10277 pResMat->PutString(pMat->GetString(i, nRowMinus1), i, 0);
10279 PushMatrix(pResMat);
10281 else
10282 PushError( FormulaError::NoRef);
10284 else if (nRow == 0)
10286 ScMatrixRef pResMat = GetNewMat(1, nR, /*bEmpty*/true);
10287 if (pResMat)
10289 SCSIZE nColMinus1 = static_cast<SCSIZE>(nCol - 1);
10290 for (SCSIZE i = 0; i < nR; i++)
10291 if (!pMat->IsStringOrEmpty(nColMinus1, i))
10292 pResMat->PutDouble(pMat->GetDouble(nColMinus1,
10293 i), i);
10294 else
10295 pResMat->PutString(pMat->GetString(nColMinus1, i), i);
10296 PushMatrix(pResMat);
10298 else
10299 PushError( FormulaError::NoRef);
10301 else
10303 if (!pMat->IsStringOrEmpty( static_cast<SCSIZE>(nCol-1),
10304 static_cast<SCSIZE>(nRow-1)))
10305 PushDouble( pMat->GetDouble(
10306 static_cast<SCSIZE>(nCol-1),
10307 static_cast<SCSIZE>(nRow-1)));
10308 else
10309 PushString( pMat->GetString(
10310 static_cast<SCSIZE>(nCol-1),
10311 static_cast<SCSIZE>(nRow-1)).getString());
10315 break;
10316 case svSingleRef:
10318 SCCOL nCol1 = 0;
10319 SCROW nRow1 = 0;
10320 SCTAB nTab1 = 0;
10321 PopSingleRef( nCol1, nRow1, nTab1);
10322 if (nCol > 1 || nRow > 1)
10323 PushError( FormulaError::NoRef);
10324 else
10325 PushSingleRef( nCol1, nRow1, nTab1);
10327 break;
10328 case svDoubleRef:
10329 case svRefList:
10331 SCCOL nCol1 = 0;
10332 SCROW nRow1 = 0;
10333 SCTAB nTab1 = 0;
10334 SCCOL nCol2 = 0;
10335 SCROW nRow2 = 0;
10336 SCTAB nTab2 = 0;
10337 bool bRowArray = false;
10338 if (GetStackType() == svRefList)
10340 FormulaConstTokenRef xRef = PopToken();
10341 if (nGlobalError != FormulaError::NONE || !xRef)
10343 PushError( FormulaError::NoRef);
10344 return;
10346 ScRange aRange( ScAddress::UNINITIALIZED);
10347 DoubleRefToRange( (*(xRef->GetRefList()))[nArea-1], aRange);
10348 aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
10349 if ( nParamCount == 2 && nRow1 == nRow2 )
10350 bRowArray = true;
10352 else
10354 PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
10355 if ( nParamCount == 2 && nRow1 == nRow2 )
10356 bRowArray = true;
10358 if ( nTab1 != nTab2 ||
10359 (nCol > 0 && nCol1+nCol-1 > nCol2) ||
10360 (nRow > 0 && nRow1+nRow-1 > nRow2 && !bRowArray ) ||
10361 ( nRow > nCol2 - nCol1 + 1 && bRowArray ))
10362 PushError( FormulaError::NoRef);
10363 else if (nCol == 0 && nRow == 0)
10365 if ( nCol1 == nCol2 && nRow1 == nRow2 )
10366 PushSingleRef( nCol1, nRow1, nTab1 );
10367 else
10368 PushDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab1 );
10370 else if (nRow == 0)
10372 if ( nRow1 == nRow2 )
10373 PushSingleRef( nCol1+nCol-1, nRow1, nTab1 );
10374 else
10375 PushDoubleRef( nCol1+nCol-1, nRow1, nTab1,
10376 nCol1+nCol-1, nRow2, nTab1 );
10378 else if (nCol == 0)
10380 if ( nCol1 == nCol2 )
10381 PushSingleRef( nCol1, nRow1+nRow-1, nTab1 );
10382 else if ( bRowArray )
10384 nCol =static_cast<SCCOL>(nRow);
10385 nRow = 1;
10386 PushSingleRef( nCol1+nCol-1, nRow1+nRow-1, nTab1);
10388 else
10389 PushDoubleRef( nCol1, nRow1+nRow-1, nTab1,
10390 nCol2, nRow1+nRow-1, nTab1);
10392 else
10393 PushSingleRef( nCol1+nCol-1, nRow1+nRow-1, nTab1);
10395 break;
10396 default:
10397 PopError();
10398 PushError( FormulaError::NoRef);
10402 void ScInterpreter::ScMultiArea()
10404 // Legacy support, convert to RefList
10405 sal_uInt8 nParamCount = GetByte();
10406 if (MustHaveParamCountMin( nParamCount, 1))
10408 while (nGlobalError == FormulaError::NONE && nParamCount-- > 1)
10410 ScUnionFunc();
10415 void ScInterpreter::ScAreas()
10417 sal_uInt8 nParamCount = GetByte();
10418 if (!MustHaveParamCount( nParamCount, 1))
10419 return;
10421 size_t nCount = 0;
10422 switch (GetStackType())
10424 case svSingleRef:
10426 FormulaConstTokenRef xT = PopToken();
10427 ValidateRef( *xT->GetSingleRef());
10428 ++nCount;
10430 break;
10431 case svDoubleRef:
10433 FormulaConstTokenRef xT = PopToken();
10434 ValidateRef( *xT->GetDoubleRef());
10435 ++nCount;
10437 break;
10438 case svRefList:
10440 FormulaConstTokenRef xT = PopToken();
10441 ValidateRef( *(xT->GetRefList()));
10442 nCount += xT->GetRefList()->size();
10444 break;
10445 default:
10446 SetError( FormulaError::IllegalParameter);
10448 PushDouble( double(nCount));
10451 void ScInterpreter::ScCurrency()
10453 sal_uInt8 nParamCount = GetByte();
10454 if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
10455 return;
10457 OUString aStr;
10458 double fDec;
10459 if (nParamCount == 2)
10461 fDec = ::rtl::math::approxFloor(GetDouble());
10462 if (fDec < -15.0 || fDec > 15.0)
10464 PushIllegalArgument();
10465 return;
10468 else
10469 fDec = 2.0;
10470 double fVal = GetDouble();
10471 double fFac;
10472 if ( fDec != 0.0 )
10473 fFac = pow( double(10), fDec );
10474 else
10475 fFac = 1.0;
10476 if (fVal < 0.0)
10477 fVal = ceil(fVal*fFac-0.5)/fFac;
10478 else
10479 fVal = floor(fVal*fFac+0.5)/fFac;
10480 const Color* pColor = nullptr;
10481 if ( fDec < 0.0 )
10482 fDec = 0.0;
10483 sal_uLong nIndex = mrContext.NFGetStandardFormat(
10484 SvNumFormatType::CURRENCY,
10485 ScGlobal::eLnge);
10486 if ( static_cast<sal_uInt16>(fDec) != mrContext.NFGetFormatPrecision( nIndex ) )
10488 OUString sFormatString = mrContext.NFGenerateFormat(
10489 nIndex,
10490 ScGlobal::eLnge,
10491 true, // with thousands separator
10492 false, // not red
10493 static_cast<sal_uInt16>(fDec));// decimal places
10494 if (!mrContext.NFGetPreviewString(sFormatString,
10495 fVal,
10496 aStr,
10497 &pColor,
10498 ScGlobal::eLnge))
10499 SetError(FormulaError::IllegalArgument);
10501 else
10503 mrContext.NFGetOutputString(fVal, nIndex, aStr, &pColor);
10505 PushString(aStr);
10508 void ScInterpreter::ScReplace()
10510 if ( !MustHaveParamCount( GetByte(), 4 ) )
10511 return;
10513 OUString aNewStr = GetString().getString();
10514 sal_Int32 nCount = GetStringPositionArgument();
10515 sal_Int32 nPos = GetStringPositionArgument();
10516 OUString aOldStr = GetString().getString();
10517 if (nPos < 1 || nCount < 0)
10518 PushIllegalArgument();
10519 else
10521 sal_Int32 nLen = aOldStr.getLength();
10522 if (nPos > nLen + 1)
10523 nPos = nLen + 1;
10524 if (nCount > nLen - nPos + 1)
10525 nCount = nLen - nPos + 1;
10526 sal_Int32 nIdx = 0;
10527 sal_Int32 nCnt = 0;
10528 while ( nIdx < nLen && nPos > nCnt + 1 )
10530 aOldStr.iterateCodePoints( &nIdx );
10531 ++nCnt;
10533 sal_Int32 nStart = nIdx;
10534 while ( nIdx < nLen && nPos + nCount - 1 > nCnt )
10536 aOldStr.iterateCodePoints( &nIdx );
10537 ++nCnt;
10539 if ( CheckStringResultLen( aOldStr, aNewStr.getLength() - (nIdx - nStart) ) )
10540 aOldStr = aOldStr.replaceAt( nStart, nIdx - nStart, aNewStr );
10541 PushString( aOldStr );
10545 void ScInterpreter::ScFixed()
10547 sal_uInt8 nParamCount = GetByte();
10548 if ( !MustHaveParamCount( nParamCount, 1, 3 ) )
10549 return;
10551 OUString aStr;
10552 double fDec;
10553 bool bThousand;
10554 if (nParamCount == 3)
10555 bThousand = !GetBool(); // Param true: no thousands separator
10556 else
10557 bThousand = true;
10558 if (nParamCount >= 2)
10560 fDec = ::rtl::math::approxFloor(GetDoubleWithDefault( 2.0 ));
10561 if (fDec < -15.0 || fDec > 15.0)
10563 PushIllegalArgument();
10564 return;
10567 else
10568 fDec = 2.0;
10569 double fVal = GetDouble();
10570 double fFac;
10571 if ( fDec != 0.0 )
10572 fFac = pow( double(10), fDec );
10573 else
10574 fFac = 1.0;
10575 if (fVal < 0.0)
10576 fVal = ceil(fVal*fFac-0.5)/fFac;
10577 else
10578 fVal = floor(fVal*fFac+0.5)/fFac;
10579 const Color* pColor = nullptr;
10580 if (fDec < 0.0)
10581 fDec = 0.0;
10582 sal_uLong nIndex = mrContext.NFGetStandardFormat(
10583 SvNumFormatType::NUMBER,
10584 ScGlobal::eLnge);
10585 OUString sFormatString = mrContext.NFGenerateFormat(
10586 nIndex,
10587 ScGlobal::eLnge,
10588 bThousand, // with thousands separator
10589 false, // not red
10590 static_cast<sal_uInt16>(fDec));// decimal places
10591 if (!mrContext.NFGetPreviewString(sFormatString,
10592 fVal,
10593 aStr,
10594 &pColor,
10595 ScGlobal::eLnge))
10596 PushIllegalArgument();
10597 else
10598 PushString(aStr);
10601 void ScInterpreter::ScFind()
10603 sal_uInt8 nParamCount = GetByte();
10604 if ( !MustHaveParamCount( nParamCount, 2, 3 ) )
10605 return;
10607 sal_Int32 nCnt;
10608 if (nParamCount == 3)
10609 nCnt = GetDouble();
10610 else
10611 nCnt = 1;
10612 OUString sStr = GetString().getString();
10613 if (nCnt < 1 || nCnt > sStr.getLength())
10614 PushNoValue();
10615 else
10617 sal_Int32 nPos = sStr.indexOf(GetString().getString(), nCnt - 1);
10618 if (nPos == -1)
10619 PushNoValue();
10620 else
10622 sal_Int32 nIdx = 0;
10623 nCnt = 0;
10624 while ( nIdx < nPos )
10626 sStr.iterateCodePoints( &nIdx );
10627 ++nCnt;
10629 PushDouble( static_cast<double>(nCnt + 1) );
10634 void ScInterpreter::ScExact()
10636 nFuncFmtType = SvNumFormatType::LOGICAL;
10637 if ( MustHaveParamCount( GetByte(), 2 ) )
10639 svl::SharedString s1 = GetString();
10640 svl::SharedString s2 = GetString();
10641 PushInt(int(s1 == s2));
10645 void ScInterpreter::ScLeft()
10647 sal_uInt8 nParamCount = GetByte();
10648 if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
10649 return;
10651 sal_Int32 n;
10652 if (nParamCount == 2)
10654 n = GetStringPositionArgument();
10655 if (n < 0)
10657 PushIllegalArgument();
10658 return ;
10661 else
10662 n = 1;
10663 OUString aStr = GetString().getString();
10664 sal_Int32 nIdx = 0;
10665 sal_Int32 nCnt = 0;
10666 while ( nIdx < aStr.getLength() && n > nCnt++ )
10667 aStr.iterateCodePoints( &nIdx );
10668 aStr = aStr.copy( 0, nIdx );
10669 PushString( aStr );
10672 namespace {
10674 struct UBlockScript {
10675 UBlockCode from;
10676 UBlockCode to;
10681 const UBlockScript scriptList[] = {
10682 {UBLOCK_HANGUL_JAMO, UBLOCK_HANGUL_JAMO},
10683 {UBLOCK_CJK_RADICALS_SUPPLEMENT, UBLOCK_HANGUL_SYLLABLES},
10684 {UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS,UBLOCK_CJK_RADICALS_SUPPLEMENT },
10685 {UBLOCK_IDEOGRAPHIC_DESCRIPTION_CHARACTERS,UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS},
10686 {UBLOCK_CJK_COMPATIBILITY_FORMS, UBLOCK_CJK_COMPATIBILITY_FORMS},
10687 {UBLOCK_HALFWIDTH_AND_FULLWIDTH_FORMS, UBLOCK_HALFWIDTH_AND_FULLWIDTH_FORMS},
10688 {UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B, UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT},
10689 {UBLOCK_CJK_STROKES, UBLOCK_CJK_STROKES}
10691 static bool IsDBCS(sal_Unicode currentChar)
10693 // for the locale of ja-JP, character U+0x005c and U+0x20ac should be ScriptType::Asian
10694 if( (currentChar == 0x005c || currentChar == 0x20ac) &&
10695 (MsLangId::getConfiguredSystemLanguage() == LANGUAGE_JAPANESE) )
10696 return true;
10697 sal_uInt16 i;
10698 bool bRet = false;
10699 UBlockCode block = ublock_getCode(currentChar);
10700 for ( i = 0; i < SAL_N_ELEMENTS(scriptList); i++) {
10701 if (block <= scriptList[i].to) break;
10703 bRet = (i < SAL_N_ELEMENTS(scriptList) && block >= scriptList[i].from);
10704 return bRet;
10706 static sal_Int32 lcl_getLengthB( std::u16string_view str, sal_Int32 nPos )
10708 sal_Int32 index = 0;
10709 sal_Int32 length = 0;
10710 while ( index < nPos )
10712 if (IsDBCS(str[index]))
10713 length += 2;
10714 else
10715 length++;
10716 index++;
10718 return length;
10720 static sal_Int32 getLengthB(std::u16string_view str)
10722 if(str.empty())
10723 return 0;
10724 else
10725 return lcl_getLengthB( str, str.size() );
10727 void ScInterpreter::ScLenB()
10729 PushDouble( getLengthB(GetString().getString()) );
10731 static OUString lcl_RightB(const OUString &rStr, sal_Int32 n)
10733 if( n < getLengthB(rStr) )
10735 OUStringBuffer aBuf(rStr);
10736 sal_Int32 index = aBuf.getLength();
10737 while(index-- >= 0)
10739 if(0 == n)
10741 aBuf.remove( 0, index + 1);
10742 break;
10744 if(-1 == n)
10746 aBuf.remove( 0, index + 2 );
10747 aBuf.insert( 0, " ");
10748 break;
10750 if(IsDBCS(aBuf[index]))
10751 n -= 2;
10752 else
10753 n--;
10755 return aBuf.makeStringAndClear();
10757 return rStr;
10759 void ScInterpreter::ScRightB()
10761 sal_uInt8 nParamCount = GetByte();
10762 if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
10763 return;
10765 sal_Int32 n;
10766 if (nParamCount == 2)
10768 n = GetStringPositionArgument();
10769 if (n < 0)
10771 PushIllegalArgument();
10772 return ;
10775 else
10776 n = 1;
10777 OUString aStr(lcl_RightB(GetString().getString(), n));
10778 PushString( aStr );
10780 static OUString lcl_LeftB(const OUString &rStr, sal_Int32 n)
10782 if( n < getLengthB(rStr) )
10784 OUStringBuffer aBuf(rStr);
10785 sal_Int32 index = -1;
10786 while(index++ < aBuf.getLength())
10788 if(0 == n)
10790 aBuf.truncate(index);
10791 break;
10793 if(-1 == n)
10795 aBuf.truncate( index - 1 );
10796 aBuf.append(" ");
10797 break;
10799 if(IsDBCS(aBuf[index]))
10800 n -= 2;
10801 else
10802 n--;
10804 return aBuf.makeStringAndClear();
10806 return rStr;
10808 void ScInterpreter::ScLeftB()
10810 sal_uInt8 nParamCount = GetByte();
10811 if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
10812 return;
10814 sal_Int32 n;
10815 if (nParamCount == 2)
10817 n = GetStringPositionArgument();
10818 if (n < 0)
10820 PushIllegalArgument();
10821 return ;
10824 else
10825 n = 1;
10826 OUString aStr(lcl_LeftB(GetString().getString(), n));
10827 PushString( aStr );
10829 void ScInterpreter::ScMidB()
10831 if ( !MustHaveParamCount( GetByte(), 3 ) )
10832 return;
10834 const sal_Int32 nCount = GetStringPositionArgument();
10835 const sal_Int32 nStart = GetStringPositionArgument();
10836 OUString aStr = GetString().getString();
10837 if (nStart < 1 || nCount < 0)
10838 PushIllegalArgument();
10839 else
10842 aStr = lcl_LeftB(aStr, nStart + nCount - 1);
10843 sal_Int32 nCnt = getLengthB(aStr) - nStart + 1;
10844 aStr = lcl_RightB(aStr, std::max<sal_Int32>(nCnt,0));
10845 PushString(aStr);
10849 void ScInterpreter::ScReplaceB()
10851 if ( !MustHaveParamCount( GetByte(), 4 ) )
10852 return;
10854 OUString aNewStr = GetString().getString();
10855 const sal_Int32 nCount = GetStringPositionArgument();
10856 const sal_Int32 nPos = GetStringPositionArgument();
10857 OUString aOldStr = GetString().getString();
10858 int nLen = getLengthB( aOldStr );
10859 if (nPos < 1.0 || nPos > nLen || nCount < 0.0 || nPos + nCount -1 > nLen)
10860 PushIllegalArgument();
10861 else
10863 // REPLACEB(aOldStr;nPos;nCount;aNewStr) is the same as
10864 // LEFTB(aOldStr;nPos-1) & aNewStr & RIGHT(aOldStr;LENB(aOldStr)-(nPos - 1)-nCount)
10865 OUString aStr1 = lcl_LeftB( aOldStr, nPos - 1 );
10866 OUString aStr3 = lcl_RightB( aOldStr, nLen - nPos - nCount + 1);
10868 PushString( aStr1 + aNewStr + aStr3 );
10872 void ScInterpreter::ScFindB()
10874 sal_uInt8 nParamCount = GetByte();
10875 if ( !MustHaveParamCount( nParamCount, 2, 3 ) )
10876 return;
10878 sal_Int32 nStart;
10879 if ( nParamCount == 3 )
10880 nStart = GetStringPositionArgument();
10881 else
10882 nStart = 1;
10883 OUString aStr = GetString().getString();
10884 int nLen = getLengthB( aStr );
10885 OUString asStr = GetString().getString();
10886 int nsLen = getLengthB( asStr );
10887 if ( nStart < 1 || nStart > nLen - nsLen + 1 )
10888 PushIllegalArgument();
10889 else
10891 // create a string from sStr starting at nStart
10892 OUString aBuf = lcl_RightB( aStr, nLen - nStart + 1 );
10893 // search aBuf for asStr
10894 sal_Int32 nPos = aBuf.indexOf( asStr, 0 );
10895 if ( nPos == -1 )
10896 PushNoValue();
10897 else
10899 // obtain byte value of nPos
10900 int nBytePos = lcl_getLengthB( aBuf, nPos );
10901 PushDouble( nBytePos + nStart );
10906 void ScInterpreter::ScSearchB()
10908 sal_uInt8 nParamCount = GetByte();
10909 if ( !MustHaveParamCount( nParamCount, 2, 3 ) )
10910 return;
10912 sal_Int32 nStart;
10913 if ( nParamCount == 3 )
10915 nStart = GetStringPositionArgument();
10916 if( nStart < 1 )
10918 PushIllegalArgument();
10919 return;
10922 else
10923 nStart = 1;
10924 OUString aStr = GetString().getString();
10925 sal_Int32 nLen = getLengthB( aStr );
10926 OUString asStr = GetString().getString();
10927 sal_Int32 nsLen = nStart - 1;
10928 if( nsLen >= nLen )
10929 PushNoValue();
10930 else
10932 // create a string from sStr starting at nStart
10933 OUString aSubStr( lcl_RightB( aStr, nLen - nStart + 1 ) );
10934 // search aSubStr for asStr
10935 sal_Int32 nPos = 0;
10936 sal_Int32 nEndPos = aSubStr.getLength();
10937 utl::SearchParam::SearchType eSearchType = DetectSearchType( asStr, mrDoc );
10938 utl::SearchParam sPar( asStr, eSearchType, false, '~', false );
10939 utl::TextSearch sT( sPar, ScGlobal::getCharClass() );
10940 if ( !sT.SearchForward( aSubStr, &nPos, &nEndPos ) )
10941 PushNoValue();
10942 else
10944 // obtain byte value of nPos
10945 int nBytePos = lcl_getLengthB( aSubStr, nPos );
10946 PushDouble( nBytePos + nStart );
10951 void ScInterpreter::ScRight()
10953 sal_uInt8 nParamCount = GetByte();
10954 if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
10955 return;
10957 sal_Int32 n;
10958 if (nParamCount == 2)
10960 n = GetStringPositionArgument();
10961 if (n < 0)
10963 PushIllegalArgument();
10964 return ;
10967 else
10968 n = 1;
10969 OUString aStr = GetString().getString();
10970 sal_Int32 nLen = aStr.getLength();
10971 if ( nLen <= n )
10972 PushString( aStr );
10973 else
10975 sal_Int32 nIdx = nLen;
10976 sal_Int32 nCnt = 0;
10977 while ( nIdx > 0 && n > nCnt )
10979 aStr.iterateCodePoints( &nIdx, -1 );
10980 ++nCnt;
10982 aStr = aStr.copy( nIdx, nLen - nIdx );
10983 PushString( aStr );
10987 void ScInterpreter::ScSearch()
10989 sal_uInt8 nParamCount = GetByte();
10990 if ( !MustHaveParamCount( nParamCount, 2, 3 ) )
10991 return;
10993 sal_Int32 nStart;
10994 if (nParamCount == 3)
10996 nStart = GetStringPositionArgument();
10997 if( nStart < 1 )
10999 PushIllegalArgument();
11000 return;
11003 else
11004 nStart = 1;
11005 OUString sStr = GetString().getString();
11006 OUString SearchStr = GetString().getString();
11007 sal_Int32 nPos = nStart - 1;
11008 sal_Int32 nEndPos = sStr.getLength();
11009 if( nPos >= nEndPos )
11010 PushNoValue();
11011 else
11013 utl::SearchParam::SearchType eSearchType = DetectSearchType( SearchStr, mrDoc );
11014 utl::SearchParam sPar(SearchStr, eSearchType, false, '~', false);
11015 utl::TextSearch sT( sPar, ScGlobal::getCharClass() );
11016 bool bBool = sT.SearchForward(sStr, &nPos, &nEndPos);
11017 if (!bBool)
11018 PushNoValue();
11019 else
11021 sal_Int32 nIdx = 0;
11022 sal_Int32 nCnt = 0;
11023 while ( nIdx < nPos )
11025 sStr.iterateCodePoints( &nIdx );
11026 ++nCnt;
11028 PushDouble( static_cast<double>(nCnt + 1) );
11033 void ScInterpreter::ScRegex()
11035 const sal_uInt8 nParamCount = GetByte();
11036 if (!MustHaveParamCount( nParamCount, 2, 4))
11037 return;
11039 // Flags are supported only for replacement, search match flags can be
11040 // individually and much more flexible set in the regular expression
11041 // pattern using (?ismwx-ismwx)
11042 bool bGlobalReplacement = false;
11043 sal_Int32 nOccurrence = 1; // default first occurrence, if any
11044 if (nParamCount == 4)
11046 // Argument can be either string or double.
11047 double fOccurrence;
11048 svl::SharedString aFlagsString;
11049 bool bDouble;
11050 if (!IsMissing())
11051 bDouble = GetDoubleOrString( fOccurrence, aFlagsString);
11052 else
11054 // For an omitted argument keep the default.
11055 PopError();
11056 bDouble = true;
11057 fOccurrence = nOccurrence;
11059 if (nGlobalError != FormulaError::NONE)
11061 PushError( nGlobalError);
11062 return;
11064 if (bDouble)
11066 if (!CheckStringPositionArgument( fOccurrence))
11068 PushError( FormulaError::IllegalArgument);
11069 return;
11071 nOccurrence = static_cast<sal_Int32>(fOccurrence);
11073 else
11075 const OUString& aFlags( aFlagsString.getString());
11076 // Empty flags string is valid => no flag set.
11077 if (aFlags.getLength() > 1)
11079 // Only one flag supported.
11080 PushIllegalArgument();
11081 return;
11083 if (aFlags.getLength() == 1)
11085 if (aFlags.indexOf('g') >= 0)
11086 bGlobalReplacement = true;
11087 else
11089 // Unsupported flag.
11090 PushIllegalArgument();
11091 return;
11097 bool bReplacement = false;
11098 OUString aReplacement;
11099 if (nParamCount >= 3)
11101 // A missing argument is not an empty string to replace the match.
11102 // nOccurrence==0 forces no replacement, so simply discard the
11103 // argument.
11104 if (IsMissing() || nOccurrence == 0)
11105 PopError();
11106 else
11108 aReplacement = GetString().getString();
11109 bReplacement = true;
11112 // If bGlobalReplacement==true and bReplacement==false then
11113 // bGlobalReplacement is silently ignored.
11115 const OUString aExpression = GetString().getString();
11116 const OUString aText = GetString().getString();
11118 if (nGlobalError != FormulaError::NONE)
11120 PushError( nGlobalError);
11121 return;
11124 // 0-th match or replacement is none, return original string early.
11125 if (nOccurrence == 0)
11127 PushString( aText);
11128 return;
11131 const icu::UnicodeString aIcuExpression(
11132 false, reinterpret_cast<const UChar*>(aExpression.getStr()), aExpression.getLength());
11133 UErrorCode status = U_ZERO_ERROR;
11134 icu::RegexMatcher aRegexMatcher( aIcuExpression, 0, status);
11135 if (U_FAILURE(status))
11137 // Invalid regex.
11138 PushIllegalArgument();
11139 return;
11141 // Guard against pathological patterns, limit steps of engine, see
11142 // https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1RegexMatcher.html#a6ebcfcab4fe6a38678c0291643a03a00
11143 aRegexMatcher.setTimeLimit( 23*1000, status);
11145 const icu::UnicodeString aIcuText(false, reinterpret_cast<const UChar*>(aText.getStr()), aText.getLength());
11146 aRegexMatcher.reset( aIcuText);
11148 if (!bReplacement)
11150 // Find n-th occurrence.
11151 sal_Int32 nCount = 0;
11152 while (aRegexMatcher.find(status) && U_SUCCESS(status) && ++nCount < nOccurrence)
11154 if (U_FAILURE(status))
11156 // Some error.
11157 PushIllegalArgument();
11158 return;
11160 // n-th match found?
11161 if (nCount != nOccurrence)
11163 PushError( FormulaError::NotAvailable);
11164 return;
11166 // Extract matched text.
11167 icu::UnicodeString aMatch( aRegexMatcher.group( status));
11168 if (U_FAILURE(status))
11170 // Some error.
11171 PushIllegalArgument();
11172 return;
11174 OUString aResult( reinterpret_cast<const sal_Unicode*>(aMatch.getBuffer()), aMatch.length());
11175 PushString( aResult);
11176 return;
11179 const icu::UnicodeString aIcuReplacement(
11180 false, reinterpret_cast<const UChar*>(aReplacement.getStr()), aReplacement.getLength());
11181 icu::UnicodeString aReplaced;
11182 if (bGlobalReplacement)
11183 // Replace all occurrences of match with replacement.
11184 aReplaced = aRegexMatcher.replaceAll( aIcuReplacement, status);
11185 else if (nOccurrence == 1)
11186 // Replace first occurrence of match with replacement.
11187 aReplaced = aRegexMatcher.replaceFirst( aIcuReplacement, status);
11188 else
11190 // Replace n-th occurrence of match with replacement.
11191 sal_Int32 nCount = 0;
11192 while (aRegexMatcher.find(status) && U_SUCCESS(status))
11194 // XXX NOTE: After several RegexMatcher::find() the
11195 // RegexMatcher::appendReplacement() still starts at the
11196 // beginning (or after the last appendReplacement() position
11197 // which is none here) and copies the original text up to the
11198 // current found match and then replaces the found match.
11199 if (++nCount == nOccurrence)
11201 aRegexMatcher.appendReplacement( aReplaced, aIcuReplacement, status);
11202 break;
11205 aRegexMatcher.appendTail( aReplaced);
11207 if (U_FAILURE(status))
11209 // Some error, e.g. extraneous $1 without group.
11210 PushIllegalArgument();
11211 return;
11213 OUString aResult( reinterpret_cast<const sal_Unicode*>(aReplaced.getBuffer()), aReplaced.length());
11214 PushString( aResult);
11217 void ScInterpreter::ScMid()
11219 if ( !MustHaveParamCount( GetByte(), 3 ) )
11220 return;
11222 const sal_Int32 nSubLen = GetStringPositionArgument();
11223 const sal_Int32 nStart = GetStringPositionArgument();
11224 OUString aStr = GetString().getString();
11225 if ( nStart < 1 || nSubLen < 0 )
11226 PushIllegalArgument();
11227 else if (nStart > kScInterpreterMaxStrLen || nSubLen > kScInterpreterMaxStrLen)
11228 PushError(FormulaError::StringOverflow);
11229 else
11231 sal_Int32 nLen = aStr.getLength();
11232 sal_Int32 nIdx = 0;
11233 sal_Int32 nCnt = 0;
11234 while ( nIdx < nLen && nStart - 1 > nCnt )
11236 aStr.iterateCodePoints( &nIdx );
11237 ++nCnt;
11239 sal_Int32 nIdx0 = nIdx; //start position
11241 while ( nIdx < nLen && nStart + nSubLen - 1 > nCnt )
11243 aStr.iterateCodePoints( &nIdx );
11244 ++nCnt;
11246 aStr = aStr.copy( nIdx0, nIdx - nIdx0 );
11247 PushString( aStr );
11251 void ScInterpreter::ScText()
11253 if ( !MustHaveParamCount( GetByte(), 2 ) )
11254 return;
11256 OUString sFormatString = GetString().getString();
11257 svl::SharedString aStr;
11258 bool bString = false;
11259 double fVal = 0.0;
11260 switch (GetStackType())
11262 case svError:
11263 PopError();
11264 break;
11265 case svDouble:
11266 fVal = PopDouble();
11267 break;
11268 default:
11270 FormulaConstTokenRef xTok( PopToken());
11271 if (nGlobalError == FormulaError::NONE)
11273 PushTokenRef( xTok);
11274 // Temporarily override the ConvertStringToValue()
11275 // error for GetCellValue() / GetCellValueOrZero()
11276 FormulaError nSErr = mnStringNoValueError;
11277 mnStringNoValueError = FormulaError::NotNumericString;
11278 fVal = GetDouble();
11279 mnStringNoValueError = nSErr;
11280 if (nGlobalError == FormulaError::NotNumericString)
11282 // Not numeric.
11283 nGlobalError = FormulaError::NONE;
11284 PushTokenRef( xTok);
11285 aStr = GetString();
11286 bString = true;
11291 if (nGlobalError != FormulaError::NONE)
11292 PushError( nGlobalError);
11293 else if (sFormatString.isEmpty())
11295 // Mimic the Excel behaviour that
11296 // * anything numeric returns an empty string
11297 // * text convertible to numeric returns an empty string
11298 // * any other text returns that text
11299 // Conversion was detected above.
11300 if (bString)
11301 PushString( aStr);
11302 else
11303 PushString( OUString());
11305 else
11307 OUString aResult;
11308 const Color* pColor = nullptr;
11309 LanguageType eCellLang;
11310 const ScPatternAttr* pPattern = mrDoc.GetPattern(
11311 aPos.Col(), aPos.Row(), aPos.Tab() );
11312 if ( pPattern )
11313 eCellLang = pPattern->GetItem( ATTR_LANGUAGE_FORMAT ).GetValue();
11314 else
11315 eCellLang = ScGlobal::eLnge;
11316 if (bString)
11318 if (!mrContext.NFGetPreviewString( sFormatString, aStr.getString(),
11319 aResult, &pColor, eCellLang))
11320 PushIllegalArgument();
11321 else
11322 PushString( aResult);
11324 else
11326 if (!mrContext.NFGetPreviewStringGuess( sFormatString, fVal,
11327 aResult, &pColor, eCellLang))
11328 PushIllegalArgument();
11329 else
11330 PushString( aResult);
11335 void ScInterpreter::ScSubstitute()
11337 sal_uInt8 nParamCount = GetByte();
11338 if ( !MustHaveParamCount( nParamCount, 3, 4 ) )
11339 return;
11341 sal_Int32 nCnt;
11342 if (nParamCount == 4)
11344 nCnt = GetStringPositionArgument();
11345 if (nCnt < 1)
11347 PushIllegalArgument();
11348 return;
11351 else
11352 nCnt = 0;
11353 OUString sNewStr = GetString().getString();
11354 OUString sOldStr = GetString().getString();
11355 OUString sStr = GetString().getString();
11356 sal_Int32 nPos = 0;
11357 sal_Int32 nCount = 0;
11358 std::optional<OUStringBuffer> oResult;
11359 for (sal_Int32 nEnd = sStr.indexOf(sOldStr); nEnd >= 0; nEnd = sStr.indexOf(sOldStr, nEnd))
11361 if (nCnt == 0 || ++nCount == nCnt) // Found a replacement cite
11363 if (!oResult) // Only allocate buffer when needed
11364 oResult.emplace(sStr.getLength() + sNewStr.getLength() - sOldStr.getLength());
11366 oResult->append(sStr.subView(nPos, nEnd - nPos)); // Copy leading unchanged text
11367 if (!CheckStringResultLen(*oResult, sNewStr.getLength()))
11368 return PushError(GetError());
11369 oResult->append(sNewStr); // Copy the replacement
11370 nPos = nEnd + sOldStr.getLength();
11371 if (nCnt > 0) // Found the single replacement site - end the loop
11372 break;
11374 nEnd += sOldStr.getLength();
11376 if (oResult) // If there were prior replacements, copy the rest, otherwise use original
11377 oResult->append(sStr.subView(nPos, sStr.getLength() - nPos));
11378 PushString(oResult ? oResult->makeStringAndClear() : sStr);
11381 void ScInterpreter::ScRept()
11383 if ( !MustHaveParamCount( GetByte(), 2 ) )
11384 return;
11386 sal_Int32 nCnt = GetStringPositionArgument();
11387 OUString aStr = GetString().getString();
11388 if (nCnt < 0)
11389 PushIllegalArgument();
11390 else if (static_cast<double>(nCnt) * aStr.getLength() > kScInterpreterMaxStrLen)
11392 PushError( FormulaError::StringOverflow );
11394 else if (nCnt == 0)
11395 PushString( OUString() );
11396 else
11398 const sal_Int32 nLen = aStr.getLength();
11399 OUStringBuffer aRes(nCnt*nLen);
11400 while( nCnt-- )
11401 aRes.append(aStr);
11402 PushString( aRes.makeStringAndClear() );
11406 void ScInterpreter::ScConcat()
11408 sal_uInt8 nParamCount = GetByte();
11410 //reverse order of parameter stack to simplify processing
11411 ReverseStack(nParamCount);
11413 OUStringBuffer aRes;
11414 while( nParamCount-- > 0)
11416 OUString aStr = GetString().getString();
11417 if (CheckStringResultLen(aRes, aStr.getLength()))
11418 aRes.append(aStr);
11419 else
11420 break;
11422 PushString( aRes.makeStringAndClear() );
11425 FormulaError ScInterpreter::GetErrorType()
11427 FormulaError nErr;
11428 FormulaError nOldError = nGlobalError;
11429 nGlobalError = FormulaError::NONE;
11430 switch ( GetStackType() )
11432 case svRefList :
11434 FormulaConstTokenRef x = PopToken();
11435 if (nGlobalError != FormulaError::NONE)
11436 nErr = nGlobalError;
11437 else
11439 const ScRefList* pRefList = x->GetRefList();
11440 size_t n = pRefList->size();
11441 if (!n)
11442 nErr = FormulaError::NoRef;
11443 else if (n > 1)
11444 nErr = FormulaError::NoValue;
11445 else
11447 ScRange aRange;
11448 DoubleRefToRange( (*pRefList)[0], aRange);
11449 if (nGlobalError != FormulaError::NONE)
11450 nErr = nGlobalError;
11451 else
11453 ScAddress aAdr;
11454 if ( DoubleRefToPosSingleRef( aRange, aAdr ) )
11455 nErr = mrDoc.GetErrCode( aAdr );
11456 else
11457 nErr = nGlobalError;
11462 break;
11463 case svDoubleRef :
11465 ScRange aRange;
11466 PopDoubleRef( aRange );
11467 if ( nGlobalError != FormulaError::NONE )
11468 nErr = nGlobalError;
11469 else
11471 ScAddress aAdr;
11472 if ( DoubleRefToPosSingleRef( aRange, aAdr ) )
11473 nErr = mrDoc.GetErrCode( aAdr );
11474 else
11475 nErr = nGlobalError;
11478 break;
11479 case svSingleRef :
11481 ScAddress aAdr;
11482 PopSingleRef( aAdr );
11483 if ( nGlobalError != FormulaError::NONE )
11484 nErr = nGlobalError;
11485 else
11486 nErr = mrDoc.GetErrCode( aAdr );
11488 break;
11489 default:
11490 PopError();
11491 nErr = nGlobalError;
11493 nGlobalError = nOldError;
11494 return nErr;
11497 void ScInterpreter::ScErrorType()
11499 FormulaError nErr = GetErrorType();
11500 if ( nErr != FormulaError::NONE )
11502 nGlobalError = FormulaError::NONE;
11503 PushDouble( static_cast<double>(nErr) );
11505 else
11507 PushNA();
11511 void ScInterpreter::ScErrorType_ODF()
11513 FormulaError nErr = GetErrorType();
11514 sal_uInt16 nErrType;
11516 switch ( nErr )
11518 case FormulaError::NoCode : // #NULL!
11519 nErrType = 1;
11520 break;
11521 case FormulaError::DivisionByZero : // #DIV/0!
11522 nErrType = 2;
11523 break;
11524 case FormulaError::NoValue : // #VALUE!
11525 nErrType = 3;
11526 break;
11527 case FormulaError::NoRef : // #REF!
11528 nErrType = 4;
11529 break;
11530 case FormulaError::NoName : // #NAME?
11531 nErrType = 5;
11532 break;
11533 case FormulaError::IllegalFPOperation : // #NUM!
11534 nErrType = 6;
11535 break;
11536 case FormulaError::NotAvailable : // #N/A
11537 nErrType = 7;
11538 break;
11540 #GETTING_DATA is a message that can appear in Excel when a large or
11541 complex worksheet is being calculated. In Excel 2007 and newer,
11542 operations are grouped so more complicated cells may finish after
11543 earlier ones do. While the calculations are still processing, the
11544 unfinished cells may display #GETTING_DATA.
11545 Because the message is temporary and disappears when the calculations
11546 complete, this isn’t a true error.
11547 No calc error code known (yet).
11549 case : // GETTING_DATA
11550 nErrType = 8;
11551 break;
11553 default :
11554 nErrType = 0;
11555 break;
11558 if ( nErrType )
11560 nGlobalError =FormulaError::NONE;
11561 PushDouble( nErrType );
11563 else
11564 PushNA();
11567 static bool MayBeRegExp( std::u16string_view rStr )
11569 if ( rStr.empty() || (rStr.size() == 1 && rStr[0] != '.') )
11570 return false; // single meta characters can not be a regexp
11571 // First two characters are wildcard '?' and '*' characters.
11572 std::u16string_view cre(u"?*+.[]^$\\<>()|");
11573 return rStr.find_first_of(cre) != std::u16string_view::npos;
11576 static bool MayBeWildcard( std::u16string_view rStr )
11578 // Wildcards with '~' escape, if there are no wildcards then an escaped
11579 // character does not make sense, but it modifies the search pattern in an
11580 // Excel compatible wildcard search...
11581 std::u16string_view cw(u"*?~");
11582 return rStr.find_first_of(cw) != std::u16string_view::npos;
11585 utl::SearchParam::SearchType ScInterpreter::DetectSearchType( std::u16string_view rStr, const ScDocument& rDoc )
11587 const auto eType = rDoc.GetDocOptions().GetFormulaSearchType();
11588 if ((eType == utl::SearchParam::SearchType::Wildcard && MayBeWildcard(rStr))
11589 || (eType == utl::SearchParam::SearchType::Regexp && MayBeRegExp(rStr)))
11590 return eType;
11591 return utl::SearchParam::SearchType::Normal;
11594 bool ScInterpreter::SearchMatrixForValue( VectorSearchArguments& vsa, ScQueryParam& rParam, ScQueryEntry& rEntry, ScQueryEntry::Item& rItem )
11596 SCSIZE nC, nR;
11597 vsa.pMatSrc->GetDimensions( nC, nR);
11598 if (nC > 1 && nR > 1)
11600 // The source matrix must be a vector.
11601 PushIllegalParameter();
11602 return false;
11604 vsa.bVLookup = ( nC == 1 );
11606 // Do not propagate errors from matrix while searching.
11607 vsa.pMatSrc->SetErrorInterpreter( nullptr );
11609 SCSIZE nMatCount = (vsa.bVLookup ? nR : nC);
11610 VectorMatrixAccessor aMatAcc(*(vsa.pMatSrc), vsa.bVLookup);
11611 bool bMatchWholeCell = mrDoc.GetDocOptions().IsMatchWholeCell();
11613 switch ( vsa.eSearchMode )
11615 case searchfwd :
11617 switch ( vsa.eMatchMode )
11619 case exactorNA :
11620 case wildcard :
11621 // simple serial search for equality mode (source data doesn't
11622 // need to be sorted).
11623 for (SCSIZE i = 0; i < nMatCount; ++i)
11625 if (lcl_CompareMatrix2Query( i, aMatAcc, rParam, rEntry, bMatchWholeCell ) == 0)
11627 vsa.nHitIndex = i+1; // found !
11628 break;
11631 break;
11633 case exactorS :
11634 case exactorG :
11635 for (SCSIZE i = 0; i < nMatCount; ++i)
11637 sal_Int32 result = lcl_CompareMatrix2Query( i, aMatAcc, rParam, rEntry, bMatchWholeCell );
11638 if (result == 0)
11640 vsa.nHitIndex = i+1; // found !
11641 break;
11643 else if (vsa.eMatchMode == exactorS && result == -1)
11645 if ( vsa.nBestFit == SCSIZE_MAX )
11646 vsa.nBestFit = i;
11647 else
11649 // replace value of vsa.nBestFit if value(i) > value(vsa.nBestFit)
11650 if ( lcl_Compare2MatrixCells( i, aMatAcc, vsa.nBestFit) == 1 )
11651 vsa.nBestFit = i;
11654 else if (vsa.eMatchMode == exactorG && result == 1)
11656 if ( vsa.nBestFit == SCSIZE_MAX )
11657 vsa.nBestFit = i;
11658 else
11660 // replace value of vsa.nBestFit if value(i) < value(vsa.nBestFit)
11661 if ( lcl_Compare2MatrixCells( i, aMatAcc, vsa.nBestFit) == -1 )
11662 vsa.nBestFit = i;
11665 // else do nothing
11667 break;
11669 default :
11670 PushIllegalParameter();
11671 return false;
11674 break;
11676 case searchrev:
11678 switch ( vsa.eMatchMode )
11680 case exactorNA :
11681 case wildcard :
11682 // simple serial search for equality mode (source data doesn't
11683 // need to be sorted).
11684 for ( SCSIZE i = nMatCount - 1; i > 0; i-- )
11686 if (lcl_CompareMatrix2Query(i, aMatAcc, rParam, rEntry, bMatchWholeCell) == 0)
11688 vsa.nHitIndex = i + 1; // found !
11689 break;
11692 break;
11694 case exactorS :
11695 case exactorG :
11696 for (SCSIZE i = nMatCount - 1; i-- > 0; )
11698 sal_Int32 result = lcl_CompareMatrix2Query( i, aMatAcc, rParam, rEntry, bMatchWholeCell );
11699 if (result == 0)
11701 vsa.nHitIndex = i + 1; // found !
11702 break;
11704 else if (vsa.eMatchMode == exactorS && result == -1)
11706 if ( vsa.nBestFit == SCSIZE_MAX )
11707 vsa.nBestFit = i;
11708 else
11710 // replace value of vsa.nBestFit if value(i) > value(vsa.nBestFit)
11711 if ( lcl_Compare2MatrixCells( i, aMatAcc, vsa.nBestFit) == 1 )
11712 vsa.nBestFit = i;
11715 else if (vsa.eMatchMode == exactorG && result == 1)
11717 if ( vsa.nBestFit == SCSIZE_MAX )
11718 vsa.nBestFit = i;
11719 else
11721 // replace value of vsa.nBestFit if value(i) < value(vsa.nBestFit)
11722 if ( lcl_Compare2MatrixCells( i, aMatAcc, vsa.nBestFit) == -1 )
11723 vsa.nBestFit = i;
11726 // else do nothing
11728 break;
11730 default :
11731 PushIllegalParameter();
11732 return false;
11735 break;
11737 case searchbasc:
11738 case searchbdesc:
11740 // binary search for non-equality mode (the source data is sorted)
11741 bool bAscOrder = ( vsa.eSearchMode == searchbasc );
11742 SCSIZE nFirst = 0;
11743 SCSIZE nLast = nMatCount - 1;
11744 for ( SCSIZE nLen = nLast - nFirst; nLen > 0; nLen = nLast - nFirst )
11746 SCSIZE nMid = nFirst + nLen / 2;
11747 sal_Int32 nCmp = lcl_CompareMatrix2Query( nMid, aMatAcc, rParam, rEntry, bMatchWholeCell );
11748 if ( nCmp == 0 )
11750 // exact match. find the last item with the same value.
11751 lcl_GetLastMatch( nMid, aMatAcc, nMatCount);
11752 vsa.nHitIndex = nMid + 1;
11753 break;
11756 if ( nLen == 1 ) // first and last items are next to each other.
11758 if ( bAscOrder && vsa.eMatchMode == exactorS )
11759 vsa.nHitIndex = ( nCmp > 0 ? nFirst : nLast );
11760 else if ( !bAscOrder && vsa.eMatchMode == exactorG )
11761 vsa.nHitIndex = ( nCmp < 0 ? nFirst : nLast );
11762 break;
11764 else
11766 if ( nCmp < 0 )
11768 if ( bAscOrder )
11769 nFirst = nMid;
11770 else
11771 nLast = nMid;
11773 else
11775 if ( bAscOrder )
11776 nLast = nMid;
11777 else
11778 nFirst = nMid;
11781 if ( vsa.nHitIndex == nMatCount - 1 ) // last item
11783 nCmp = lcl_CompareMatrix2Query( vsa.nHitIndex, aMatAcc, rParam, rEntry, bMatchWholeCell );
11784 if ( ( vsa.eMatchMode == exactorS && nCmp <= 0 ) ||
11785 ( vsa.eMatchMode == exactorG && nCmp >= 0 ) )
11787 // either the last item is an exact match or the real
11788 // hit is beyond the last item.
11789 vsa.nHitIndex++;
11791 else
11792 vsa.nHitIndex = 0;
11797 break;
11799 default:
11800 PushIllegalParameter();
11801 return false;
11804 if ((vsa.nHitIndex > 0) && ((rItem.meType == ScQueryEntry::ByString && aMatAcc.IsValue(vsa.nHitIndex - 1)) ||
11805 (rItem.meType == ScQueryEntry::ByValue && !aMatAcc.IsValue(vsa.nHitIndex - 1))))
11807 vsa.nHitIndex = 0;
11808 vsa.isResultNA = true;
11809 return false;
11811 return true;
11814 bool ScInterpreter::SearchRangeForValue( VectorSearchArguments& vsa, ScQueryParam& rParam, ScQueryEntry& rEntry )
11816 vsa.bVLookup = ( vsa.nCol1 == vsa.nCol2 );
11817 switch ( vsa.eSearchMode )
11819 case searchfwd:
11820 case searchrev:
11821 case searchbasc:
11822 case searchbdesc:
11824 if (vsa.bVLookup)
11826 // search of rows in column
11827 rParam.bByRow = true;
11828 ScAddress aResultPos( vsa.nCol1, vsa.nRow1, vsa.nTab1 );
11829 const ScComplexRefData* refData = nullptr;
11830 if ( LookupQueryWithCache( aResultPos, rParam, refData, vsa.eSearchMode, vsa.nSearchOpCode ) )
11831 vsa.nHitIndex = aResultPos.Row() - vsa.nRow1 + 1;
11833 else
11835 rParam.bByRow = false;
11836 bool bBinarySearch = vsa.eSearchMode == searchbasc || vsa.eSearchMode == searchbdesc;
11837 if (bBinarySearch && (vsa.nSearchOpCode == SC_OPCODE_X_LOOKUP || vsa.nSearchOpCode == SC_OPCODE_X_MATCH))
11839 ScQueryCellIteratorSortedCache aCellIter(mrDoc, mrContext, rParam.nTab, rParam, false, false);
11840 // Advance Entry.nField in Iterator if column changed
11841 aCellIter.SetAdvanceQueryParamEntryField(true);
11842 aCellIter.SetSortedBinarySearchMode(vsa.eSearchMode);
11843 aCellIter.SetLookupMode(vsa.nSearchOpCode);
11844 if (aCellIter.GetFirst())
11846 vsa.nHitIndex = aCellIter.GetCol() - vsa.nCol1 + 1;
11849 else
11851 // search of columns in row
11852 bool bReverseSearch = (vsa.eSearchMode == searchrev);
11853 ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, vsa.nTab1, rParam, false, bReverseSearch);
11854 // Advance Entry.nField in Iterator if column changed
11855 aCellIter.SetAdvanceQueryParamEntryField(true);
11856 aCellIter.SetLookupMode(vsa.nSearchOpCode);
11857 aCellIter.SetSortedBinarySearchMode(vsa.eSearchMode);
11858 if (rEntry.eOp == SC_EQUAL)
11860 if (aCellIter.GetFirst())
11861 vsa.nHitIndex = aCellIter.GetCol() - vsa.nCol1 + 1;
11863 else
11865 SCCOL nC;
11866 SCROW nR;
11867 if (aCellIter.FindEqualOrSortedLastInRange(nC, nR))
11868 vsa.nHitIndex = nC - vsa.nCol1 + 1;
11873 break;
11875 default :
11876 PushIllegalParameter();
11877 return false;
11879 return true;
11883 /** When search value is found, the index is stored in struct VectorSearchArguments.nIndex
11884 and SearchVectorForValue() returns true. When search value is not found or an error
11885 occurs, SearchVectorForValue() pushes the relevant (error)message and returns false,
11886 expect when SearchVectorForValue() is called by ScXLookup and the search value is not
11887 found.
11888 This difference in behaviour is because MATCH returns the found index and XLOOKUP
11889 uses the found index to determine the result(s) to be pushed and may return a custom
11890 value when the search value is not found.
11892 bool ScInterpreter::SearchVectorForValue( VectorSearchArguments& vsa )
11894 // preparations
11895 ScQueryParam rParam;
11896 rParam.nCol1 = vsa.nCol1;
11897 rParam.nRow1 = vsa.nRow1;
11898 rParam.nCol2 = vsa.nCol2;
11899 rParam.nRow2 = vsa.nRow2;
11900 rParam.nTab = vsa.nTab1;
11902 ScQueryEntry& rEntry = rParam.GetEntry(0);
11903 rEntry.nField = vsa.eSearchMode != searchrev ? vsa.nCol1 : vsa.nCol2;
11904 rEntry.bDoQuery = true;
11905 switch ( vsa.eMatchMode )
11907 case exactorNA :
11908 rEntry.eOp = SC_EQUAL;
11909 break;
11911 case exactorS :
11912 rEntry.eOp = SC_LESS_EQUAL;
11913 break;
11915 case exactorG :
11916 rEntry.eOp = SC_GREATER_EQUAL;
11917 break;
11919 case wildcard :
11920 case regex :
11921 // this mode can only used with XLOOKUP/XMATCH
11922 if ( vsa.nSearchOpCode == SC_OPCODE_X_LOOKUP || vsa.nSearchOpCode == SC_OPCODE_X_MATCH )
11924 // Wildcard/Regex search mode with binary search is not allowed
11925 if (vsa.eSearchMode == searchbasc || vsa.eSearchMode == searchbdesc)
11927 PushNoValue();
11928 return false;
11931 rEntry.eOp = SC_EQUAL;
11932 if ( vsa.isStringSearch )
11934 if (vsa.eMatchMode == wildcard && MayBeWildcard(vsa.sSearchStr.getString()))
11935 rParam.eSearchType = utl::SearchParam::SearchType::Wildcard;
11936 else if (vsa.eMatchMode == regex && MayBeRegExp(vsa.sSearchStr.getString()))
11937 rParam.eSearchType = utl::SearchParam::SearchType::Regexp;
11938 else
11939 rParam.eSearchType = utl::SearchParam::SearchType::Normal;
11942 else
11944 PushIllegalParameter();
11945 return false;
11947 break;
11949 default :
11950 PushIllegalParameter();
11951 return false;
11954 ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
11955 // allow to match empty cells as result if we are looking for the next smaller
11956 // or larger values in case of the new lookup functions
11957 if (rEntry.eOp != SC_EQUAL && (vsa.nSearchOpCode == SC_OPCODE_X_LOOKUP ||
11958 vsa.nSearchOpCode == SC_OPCODE_X_MATCH))
11959 rItem.mbMatchEmpty = true;
11961 if ( vsa.isStringSearch )
11963 rItem.meType = ScQueryEntry::ByString;
11964 rItem.maString = vsa.sSearchStr;
11965 if ( vsa.nSearchOpCode == SC_OPCODE_MATCH )
11967 if ( mrDoc.IsInVBAMode() )
11968 rParam.eSearchType = utl::SearchParam::SearchType::Wildcard;
11969 else
11970 rParam.eSearchType = DetectSearchType(rEntry.GetQueryItem().maString.getString(), mrDoc);
11973 else if ( vsa.isEmptySearch && (vsa.nSearchOpCode == SC_OPCODE_X_LOOKUP ||
11974 vsa.nSearchOpCode == SC_OPCODE_X_MATCH) )
11976 rEntry.SetQueryByEmpty();
11977 rItem.mbMatchEmpty = true;
11979 else
11981 rItem.mfVal = vsa.fSearchVal;
11982 rItem.meType = ScQueryEntry::ByValue;
11985 // execute search
11986 if (vsa.pMatSrc) // The source data is matrix array.
11988 // matrix
11989 if ( !SearchMatrixForValue( vsa, rParam, rEntry, rItem ) )
11990 return false;
11992 else
11994 // not a matrix
11995 if ( !SearchRangeForValue( vsa, rParam, rEntry ) )
11996 return false;
11999 // MATCH expects index starting with 1, XLOOKUP expects index starting with 0
12000 if ( vsa.nHitIndex > 0 )
12002 vsa.nIndex = ( vsa.nSearchOpCode == SC_OPCODE_X_LOOKUP ? --vsa.nHitIndex : vsa.nHitIndex );
12003 return true;
12005 else if ( vsa.nHitIndex == 0 && vsa.nBestFit != SCSIZE_MAX )
12007 if ( vsa.nSearchOpCode == SC_OPCODE_X_LOOKUP )
12009 vsa.nIndex = vsa.nBestFit;
12010 if ( !vsa.pMatSrc )
12012 vsa.nIndex -= ( vsa.bVLookup ? vsa.nRow1 : vsa.nCol1 );
12015 else
12017 vsa.nIndex = ++vsa.nBestFit;
12019 return true;
12022 // nomatch
12023 vsa.isResultNA = true;
12024 return false;
12027 static bool lcl_LookupQuery( ScAddress & o_rResultPos, ScDocument& rDoc, ScInterpreterContext& rContext,
12028 const ScQueryParam & rParam, const ScQueryEntry & rEntry, const ScFormulaCell* cell,
12029 const ScComplexRefData* refData, sal_Int8 nSearchMode, sal_uInt16 nOpCode )
12031 if (rEntry.eOp != SC_EQUAL)
12033 // range lookup <= or >=
12034 SCCOL nCol;
12035 SCROW nRow;
12036 bool bBinarySearch = static_cast<SearchMode>(nSearchMode) == searchbasc || static_cast<SearchMode>(nSearchMode) == searchbdesc;
12037 if ((bBinarySearch && (nOpCode == SC_OPCODE_X_LOOKUP || nOpCode == SC_OPCODE_X_MATCH)) ||
12038 ScQueryCellIteratorSortedCache::CanBeUsed(rDoc, rParam, rParam.nTab, cell, refData, rContext))
12040 ScQueryCellIteratorSortedCache aCellIter(rDoc, rContext, rParam.nTab, rParam, false, false);
12041 aCellIter.SetSortedBinarySearchMode(nSearchMode);
12042 aCellIter.SetLookupMode(nOpCode);
12043 if (aCellIter.GetFirst())
12045 o_rResultPos.SetCol(aCellIter.GetCol());
12046 o_rResultPos.SetRow(aCellIter.GetRow());
12047 return true;
12050 else
12052 bool bReverse = (static_cast<SearchMode>(nSearchMode) == searchrev);
12053 ScQueryCellIteratorDirect aCellIter(rDoc, rContext, rParam.nTab, rParam, false, bReverse);
12055 aCellIter.SetSortedBinarySearchMode(nSearchMode);
12056 aCellIter.SetLookupMode(nOpCode);
12057 if (aCellIter.FindEqualOrSortedLastInRange(nCol, nRow))
12059 o_rResultPos.SetCol(nCol);
12060 o_rResultPos.SetRow(nRow);
12061 return true;
12065 else // EQUAL
12067 // we can use binary search for rows if the SearchMode is searchbasc or searchbdesc
12068 bool bLiteral = rParam.eSearchType == utl::SearchParam::SearchType::Normal &&
12069 rEntry.GetQueryItem().meType == ScQueryEntry::ByString;
12070 bool bBinary = rParam.bByRow &&
12071 (bLiteral || rEntry.GetQueryItem().meType == ScQueryEntry::ByValue);
12073 if( bBinary && (static_cast<SearchMode>(nSearchMode) == searchbasc || static_cast<SearchMode>(nSearchMode) == searchbdesc ||
12074 ScQueryCellIteratorSortedCache::CanBeUsed(rDoc, rParam, rParam.nTab, cell, refData, rContext)))
12076 ScQueryCellIteratorSortedCache aCellIter( rDoc, rContext, rParam.nTab, rParam, false, false );
12077 aCellIter.SetSortedBinarySearchMode(nSearchMode);
12078 aCellIter.SetLookupMode(nOpCode);
12079 if (aCellIter.GetFirst())
12081 o_rResultPos.SetCol( aCellIter.GetCol());
12082 o_rResultPos.SetRow( aCellIter.GetRow());
12083 return true;
12086 else
12088 ScQueryCellIteratorDirect aCellIter( rDoc, rContext, rParam.nTab, rParam, false,
12089 static_cast<SearchMode>(nSearchMode) == searchrev);
12090 aCellIter.SetSortedBinarySearchMode(nSearchMode);
12091 aCellIter.SetLookupMode(nOpCode);
12092 if (aCellIter.GetFirst())
12094 o_rResultPos.SetCol( aCellIter.GetCol());
12095 o_rResultPos.SetRow( aCellIter.GetRow());
12096 return true;
12100 return false;
12103 // tdf#121052:
12104 // =VLOOKUP(SearchCriterion; RangeArray; Index; Sorted)
12105 // [SearchCriterion] is the value searched for in the first column of the array.
12106 // [RangeArray] is the reference, which is to comprise at least two columns.
12107 // [Index] is the number of the column in the array that contains the value to be returned. The first column has the number 1.
12109 // Prerequisite of lcl_getPrevRowWithEmptyValueLookup():
12110 // Value referenced by [SearchCriterion] is empty.
12111 // lcl_getPrevRowWithEmptyValueLookup() performs following checks:
12112 // - if we run query with "exact match" mode (i.e. VLOOKUP)
12113 // - and if we already have the same lookup done before but for another row
12114 // which is also had empty [SearchCriterion]
12116 // then
12117 // we could say, that for current row we could reuse results of the cached call which was done for the row2
12118 // In this case we return row index, which is >= 0.
12120 // Elsewhere
12121 // -1 is returned, which will lead to default behavior =>
12122 // complete lookup will be done in RangeArray inside lcl_LookupQuery() method.
12124 // This method was added only for speed up to avoid several useless complete
12125 // lookups inside [RangeArray] for searching empty strings.
12127 static SCROW lcl_getPrevRowWithEmptyValueLookup( const ScLookupCache& rCache,
12128 const ScLookupCache::QueryCriteria& rCriteria, const ScQueryParam & rParam)
12130 // is lookup value empty?
12131 const ScQueryEntry& rEntry = rParam.GetEntry(0);
12132 const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
12133 if (! rItem.maString.getString().isEmpty())
12134 return -1; // not found
12136 // try to find the row index for which we have already performed lookup
12137 // and have some result of it inside cache
12138 return rCache.lookup( rCriteria );
12141 bool ScInterpreter::LookupQueryWithCache( ScAddress & o_rResultPos,
12142 const ScQueryParam & rParam, const ScComplexRefData* refData,
12143 sal_Int8 nSearchMode, sal_uInt16 nOpCode ) const
12145 bool bFound = false;
12146 const ScQueryEntry& rEntry = rParam.GetEntry(0);
12147 bool bColumnsMatch = (rParam.nCol1 == rEntry.nField);
12148 // At least all volatile functions that generate indirect references have
12149 // to force non-cached lookup.
12150 /* TODO: We could further classify volatile functions into reference
12151 * generating and not reference generating functions to have to force less
12152 * direct lookups here. We could even further attribute volatility per
12153 * parameter so it would affect only the lookup range parameter. */
12154 if (!bColumnsMatch || GetVolatileType() != NOT_VOLATILE)
12155 bFound = lcl_LookupQuery( o_rResultPos, mrDoc, mrContext, rParam, rEntry, pMyFormulaCell,
12156 refData, nSearchMode, nOpCode );
12157 else
12159 ScRange aLookupRange( rParam.nCol1, rParam.nRow1, rParam.nTab,
12160 rParam.nCol2, rParam.nRow2, rParam.nTab);
12161 ScLookupCache& rCache = mrDoc.GetLookupCache( aLookupRange, &mrContext );
12162 ScLookupCache::QueryCriteria aCriteria( rEntry, nSearchMode);
12163 ScLookupCache::Result eCacheResult = rCache.lookup( o_rResultPos,
12164 aCriteria, aPos);
12166 // tdf#121052: Slow load of cells with VLOOKUP with references to empty cells
12167 // This check was added only for speed up to avoid several useless complete
12168 // lookups inside [RangeArray] for searching empty strings.
12169 if (eCacheResult == ScLookupCache::NOT_CACHED && aCriteria.isEmptyStringQuery())
12171 const SCROW nPrevRowWithEmptyValueLookup = lcl_getPrevRowWithEmptyValueLookup(rCache, aCriteria, rParam);
12172 if (nPrevRowWithEmptyValueLookup >= 0)
12174 // make the same lookup using cache with different row index
12175 // (this lookup was already cached)
12176 ScAddress aPosPrev(aPos);
12177 aPosPrev.SetRow(nPrevRowWithEmptyValueLookup);
12179 eCacheResult = rCache.lookup( o_rResultPos, aCriteria, aPosPrev );
12183 switch (eCacheResult)
12185 case ScLookupCache::NOT_CACHED :
12186 case ScLookupCache::CRITERIA_DIFFERENT :
12187 bFound = lcl_LookupQuery( o_rResultPos, mrDoc, mrContext, rParam, rEntry,
12188 pMyFormulaCell, refData, nSearchMode, nOpCode );
12189 if (eCacheResult == ScLookupCache::NOT_CACHED)
12190 rCache.insert( o_rResultPos, aCriteria, aPos, bFound);
12191 break;
12192 case ScLookupCache::FOUND :
12193 bFound = true;
12194 break;
12195 case ScLookupCache::NOT_AVAILABLE :
12196 ; // nothing, bFound remains FALSE
12197 break;
12200 return bFound;
12203 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */