[PATCH 4-0 4-0-0] Late Feature: IFERROR and IFNA spreadsheet functions
Eike Rathke
erack at redhat.com
Sat Jan 12 10:13:50 PST 2013
Hi,
The attached patch implements the much requested IFERROR and IFNA
spreadsheet functions on the 4-0 branch. I would like to have them as
late feature in 4-0-0 if possible though string freeze was announced
already, so 3 reviews would be needed. At least 4-0 should have it for
4-1-0
Thanks
Eike
--
LibreOffice Calc developer. Number formatter stricken i18n transpositionizer.
New GnuPG key 0x65632D3A : 2265 D7F3 A7B0 95CC 3918 630B 6A6C D5B7 6563 2D3A
Old GnuPG key 0x293C05FD : 997A 4C60 CE41 0149 0DB3 9E96 2F1A D073 293C 05FD
Support the FSFE, care about Free Software! https://fsfe.org/support/?erack
-------------- next part --------------
From cc101e678799c0f437dc02b6930fe858c560972d Mon Sep 17 00:00:00 2001
Message-Id: <cc101e678799c0f437dc02b6930fe858c560972d.1358013933.git.erack at redhat.com>
From: Eike Rathke <erack at redhat.com>
Date: Sat, 12 Jan 2013 17:48:59 +0100
Subject: [PATCH] fdo#56124 add functions IFERROR and IFNA to calc as in
ODFF1.2
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="------------erAck-patch-parts"
This is a multi-part message in MIME format.
--------------erAck-patch-parts
Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
Change-Id: Ic282e1510e121be8fe52320f1f0fe0acc4b9a652
---
formula/inc/formula/compiler.hrc | 42 +++---
formula/inc/formula/errorcodes.hxx | 3 +
formula/inc/formula/opcode.hxx | 2 +
formula/source/core/api/FormulaCompiler.cxx | 79 ++++++++--
formula/source/core/api/token.cxx | 22 ++-
formula/source/core/resource/core_resource.src | 12 ++
sc/inc/helpids.h | 2 +
sc/qa/unit/ucalc.cxx | 76 +++++++++
sc/source/core/inc/interpre.hxx | 1 +
sc/source/core/tool/interpr1.cxx | 198 ++++++++++++++++++++++--
sc/source/core/tool/interpr4.cxx | 13 +-
sc/source/core/tool/parclass.cxx | 4 +
sc/source/core/tool/token.cxx | 5 +
sc/source/filter/excel/xlformula.cxx | 13 ++-
sc/source/filter/oox/formulabase.cxx | 11 +-
sc/source/ui/src/scfuncs.src | 64 ++++++++
sc/util/hidother.src | 2 +
17 files changed, 485 insertions(+), 64 deletions(-)
--------------erAck-patch-parts
Content-Type: text/x-patch; name="0001-fdo-56124-add-functions-IFERROR-and-IFNA-to-calc-as-.patch"
Content-Transfer-Encoding: 8bit
Content-Disposition: attachment; filename="0001-fdo-56124-add-functions-IFERROR-and-IFNA-to-calc-as-.patch"
diff --git a/formula/inc/formula/compiler.hrc b/formula/inc/formula/compiler.hrc
index 582e3a5..7f976ae 100644
--- a/formula/inc/formula/compiler.hrc
+++ b/formula/inc/formula/compiler.hrc
@@ -29,26 +29,28 @@
#define SC_OPCODE_NAME 4
#define SC_OPCODE_EXTERNAL_REF 5
#define SC_OPCODE_IF 6 /* jump commands */
-#define SC_OPCODE_CHOSE 7
-#define SC_OPCODE_OPEN 8 /* parentheses and separators */
-#define SC_OPCODE_CLOSE 9
-#define SC_OPCODE_SEP 10
-#define SC_OPCODE_MISSING 11 /* special OpCodes */
-#define SC_OPCODE_BAD 12
-#define SC_OPCODE_STRINGXML 13
-#define SC_OPCODE_SPACES 14
-#define SC_OPCODE_MAT_REF 15
-#define SC_OPCODE_DB_AREA 16 /* additional access operators */
-#define SC_OPCODE_MACRO 17
-#define SC_OPCODE_COL_ROW_NAME 18
-#define SC_OPCODE_COL_ROW_NAME_AUTO 19
-#define SC_OPCODE_PERCENT_SIGN 20 /* operator _follows_ value */
-#define SC_OPCODE_ARRAY_OPEN 21
-#define SC_OPCODE_ARRAY_CLOSE 22
-#define SC_OPCODE_ARRAY_ROW_SEP 23
-#define SC_OPCODE_ARRAY_COL_SEP 24 /* some convs use sep != col_sep */
-#define SC_OPCODE_STOP_DIV 25
-#define SC_OPCODE_SKIP 26 /* used to skip raw tokens during string compilation */
+#define SC_OPCODE_IF_ERROR 7
+#define SC_OPCODE_IF_NA 8
+#define SC_OPCODE_CHOSE 9
+#define SC_OPCODE_OPEN 10 /* parentheses and separators */
+#define SC_OPCODE_CLOSE 11
+#define SC_OPCODE_SEP 12
+#define SC_OPCODE_MISSING 13 /* special OpCodes */
+#define SC_OPCODE_BAD 14
+#define SC_OPCODE_STRINGXML 15
+#define SC_OPCODE_SPACES 16
+#define SC_OPCODE_MAT_REF 17
+#define SC_OPCODE_DB_AREA 18 /* additional access operators */
+#define SC_OPCODE_MACRO 19
+#define SC_OPCODE_COL_ROW_NAME 20
+#define SC_OPCODE_COL_ROW_NAME_AUTO 21
+#define SC_OPCODE_PERCENT_SIGN 22 /* operator _follows_ value */
+#define SC_OPCODE_ARRAY_OPEN 23
+#define SC_OPCODE_ARRAY_CLOSE 24
+#define SC_OPCODE_ARRAY_ROW_SEP 25
+#define SC_OPCODE_ARRAY_COL_SEP 26 /* some convs use sep != col_sep */
+#define SC_OPCODE_STOP_DIV 27
+#define SC_OPCODE_SKIP 28 /* used to skip raw tokens during string compilation */
/*** error constants #... ***/
#define SC_OPCODE_START_ERRORS 30
diff --git a/formula/inc/formula/errorcodes.hxx b/formula/inc/formula/errorcodes.hxx
index a99dc7b..1e145e0 100644
--- a/formula/inc/formula/errorcodes.hxx
+++ b/formula/inc/formula/errorcodes.hxx
@@ -72,6 +72,9 @@ const sal_uInt16 errNestedArray = 533;
// be used to distinguish that condition from all other (inherited) errors. Do
// not use for anything else! Never push or inherit the error otherwise!
const sal_uInt16 errNotNumericString = 534;
+// ScInterpreter internal: jump matrix already has a result at this position,
+// do not overwrite in case of empty code path.
+const sal_uInt16 errJumpMatHasResult = 535;
// Interpreter: NA() not available condition, not a real error
const sal_uInt16 NOTAVAILABLE = 0x7fff;
diff --git a/formula/inc/formula/opcode.hxx b/formula/inc/formula/opcode.hxx
index cd1831b..e37395e 100644
--- a/formula/inc/formula/opcode.hxx
+++ b/formula/inc/formula/opcode.hxx
@@ -34,6 +34,8 @@ enum OpCodeEnum
ocExternalRef = SC_OPCODE_EXTERNAL_REF,
// Jump commands
ocIf = SC_OPCODE_IF,
+ ocIfError = SC_OPCODE_IF_ERROR,
+ ocIfNA = SC_OPCODE_IF_NA,
ocChose = SC_OPCODE_CHOSE,
// Parentheses and separators
ocOpen = SC_OPCODE_OPEN,
diff --git a/formula/source/core/api/FormulaCompiler.cxx b/formula/source/core/api/FormulaCompiler.cxx
index f79201b..c402113 100644
--- a/formula/source/core/api/FormulaCompiler.cxx
+++ b/formula/source/core/api/FormulaCompiler.cxx
@@ -433,6 +433,8 @@ uno::Sequence< sheet::FormulaOpCodeMapEntry > FormulaCompiler::OpCodeMap::create
// Additional functions not within range of functions.
static const sal_uInt16 aOpCodes[] = {
SC_OPCODE_IF,
+ SC_OPCODE_IF_ERROR,
+ SC_OPCODE_IF_NA,
SC_OPCODE_CHOSE,
SC_OPCODE_AND,
SC_OPCODE_OR,
@@ -824,7 +826,7 @@ sal_uInt16 FormulaCompiler::GetErrorConstant( const String& rName ) const
{
switch ((*iLook).second)
{
- // Not all may make sense in a formula, but these we know as
+ // Not all may make sense in a formula, but these we know as
// opcodes.
case ocErrNull:
nError = errNoCode;
@@ -1125,7 +1127,8 @@ void FormulaCompiler::Factor()
|| eOp == ocOr
|| eOp == ocBad
|| ( eOp >= ocInternalBegin && eOp <= ocInternalEnd )
- || (bCompileForFAP && ((eOp == ocIf) || (eOp == ocChose)))
+ || ( bCompileForFAP
+ && ( eOp == ocIf || eOp == ocIfError || eOp == ocIfNA || eOp == ocChose ) )
)
{
pFacToken = mpToken;
@@ -1174,14 +1177,25 @@ void FormulaCompiler::Factor()
pFacToken->SetByte( nSepCount );
PutCode( pFacToken );
}
- else if (eOp == ocIf || eOp == ocChose)
+ else if (eOp == ocIf || eOp == ocIfError || eOp == ocIfNA || eOp == ocChose)
{
// the PC counters are -1
pFacToken = mpToken;
- if ( eOp == ocIf )
- pFacToken->GetJump()[ 0 ] = 3; // if, else, behind
- else
- pFacToken->GetJump()[ 0 ] = MAXJUMPCOUNT+1;
+ switch (eOp)
+ {
+ case ocIf:
+ pFacToken->GetJump()[ 0 ] = 3; // if, else, behind
+ break;
+ case ocChose:
+ pFacToken->GetJump()[ 0 ] = MAXJUMPCOUNT+1;
+ break;
+ case ocIfError:
+ case ocIfNA:
+ pFacToken->GetJump()[ 0 ] = 2; // if, behind
+ break;
+ default:
+ SAL_WARN( "formula.core", "FormulaCompiler::Factor: forgot to add a jump count case?");
+ }
eOp = NextToken();
if (eOp == ocOpen)
{
@@ -1190,14 +1204,30 @@ void FormulaCompiler::Factor()
}
else
SetError(errPairExpected);
- short nJumpCount = 0;
PutCode( pFacToken );
- // during AutoCorrect (since pArr->GetCodeError() is
+ // During AutoCorrect (since pArr->GetCodeError() is
// ignored) an unlimited ocIf would crash because
// ScRawToken::Clone() allocates the JumpBuffer according to
- // nJump[0]*2+2, which is 3*2+2 on ocIf.
- const short nJumpMax =
- (pFacToken->GetOpCode() == ocIf ? 3 : MAXJUMPCOUNT);
+ // nJump[0]*2+2, which is 3*2+2 on ocIf and 2*2+2 ocIfError and ocIfNA.
+ short nJumpMax;
+ OpCode eFacOpCode = pFacToken->GetOpCode();
+ switch (eFacOpCode)
+ {
+ case ocIf:
+ nJumpMax = 3;
+ break;
+ case ocChose:
+ nJumpMax = MAXJUMPCOUNT;
+ break;
+ case ocIfError:
+ case ocIfNA:
+ nJumpMax = 2;
+ break;
+ default:
+ nJumpMax = 0;
+ SAL_WARN( "formula.core", "FormulaCompiler::Factor: forgot to add a jump max case?");
+ }
+ short nJumpCount = 0;
while ( (nJumpCount < (MAXJUMPCOUNT - 1)) && (eOp == ocSep)
&& (!pArr->GetCodeError() || bIgnoreErrors) )
{
@@ -1216,11 +1246,28 @@ void FormulaCompiler::Factor()
// always limit to nJumpMax, no arbitrary overwrites
if ( ++nJumpCount <= nJumpMax )
pFacToken->GetJump()[ nJumpCount ] = pc-1;
- if ((pFacToken->GetOpCode() == ocIf && (nJumpCount > 3)) ||
- (nJumpCount >= MAXJUMPCOUNT))
- SetError(errIllegalParameter);
- else
+ eFacOpCode = pFacToken->GetOpCode();
+ bool bLimitOk;
+ switch (eFacOpCode)
+ {
+ case ocIf:
+ bLimitOk = (nJumpCount <= 3);
+ break;
+ case ocChose:
+ bLimitOk = (nJumpCount < MAXJUMPCOUNT); /* TODO: check, really <, not <=? */
+ break;
+ case ocIfError:
+ case ocIfNA:
+ bLimitOk = (nJumpCount <= 2);
+ break;
+ default:
+ bLimitOk = false;
+ SAL_WARN( "formula.core", "FormulaCompiler::Factor: forgot to add a jump limit case?");
+ }
+ if (bLimitOk)
pFacToken->GetJump()[ 0 ] = nJumpCount;
+ else
+ SetError(errIllegalParameter);
}
}
else if ( eOp == ocMissing )
diff --git a/formula/source/core/api/token.cxx b/formula/source/core/api/token.cxx
index d187073..f6110e7c 100644
--- a/formula/source/core/api/token.cxx
+++ b/formula/source/core/api/token.cxx
@@ -76,7 +76,7 @@ bool FormulaToken::IsFunction() const
eOp != ocColRowNameAuto && eOp != ocName && eOp != ocDBArea &&
(GetByte() != 0 // x parameters
|| (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR) // no parameter
- || (ocIf == eOp || ocChose == eOp ) // @ jump commands
+ || (ocIf == eOp || ocIfError == eOp || ocIfNA == eOp || ocChose == eOp ) // @ jump commands
|| (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR) // one parameter
|| (SC_OPCODE_START_2_PAR <= eOp && eOp < SC_OPCODE_STOP_2_PAR) // x parameters (cByte==0 in
// FuncAutoPilot)
@@ -91,9 +91,10 @@ bool FormulaToken::IsFunction() const
sal_uInt8 FormulaToken::GetParamCount() const
{
if ( eOp < SC_OPCODE_STOP_DIV && eOp != ocExternal && eOp != ocMacro &&
- eOp != ocIf && eOp != ocChose && eOp != ocPercentSign )
+ eOp != ocIf && eOp != ocIfError && eOp != ocIfNA && eOp != ocChose &&
+ eOp != ocPercentSign )
return 0; // parameters and specials
- // ocIf and ocChose not for FAP, have cByte then
+ // ocIf, ocIfError, ocIfNA and ocChose not for FAP, have cByte then
//2do: bool parameter whether FAP or not?
else if ( GetByte() )
return GetByte(); // all functions, also ocExternal and ocMacro
@@ -106,7 +107,7 @@ sal_uInt8 FormulaToken::GetParamCount() const
return 0; // no parameter
else if (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR)
return 1; // one parameter
- else if ( eOp == ocIf || eOp == ocChose )
+ else if ( eOp == ocIf || eOp == ocIfError || eOp == ocIfNA || eOp == ocChose )
return 1; // only the condition counts as parameter
else
return 0; // all the rest, no Parameter, or
@@ -842,8 +843,8 @@ bool FormulaTokenArray::HasMatrixDoubleRefOps()
}
if ( eOp == ocPush || lcl_IsReference( eOp, t->GetType() ) )
pStack[sp++] = t;
- else if ( eOp == ocIf || eOp == ocChose )
- { // Jumps ignorieren, vorheriges Result (Condition) poppen
+ else if ( eOp == ocIf || eOp == ocIfError || eOp == ocIfNA || eOp == ocChose )
+ { // ignore Jumps, pop previous Result (Condition)
if ( sp )
--sp;
}
@@ -1178,10 +1179,17 @@ FormulaToken* FormulaTokenArray::AddOpCode( OpCode eOp )
pRet = new FormulaToken( svSep,eOp );
break;
case ocIf:
+ case ocIfError:
+ case ocIfNA:
case ocChose:
{
short nJump[MAXJUMPCOUNT + 1];
- nJump[ 0 ] = ocIf == eOp ? 3 : MAXJUMPCOUNT+1;
+ if ( eOp == ocIf )
+ nJump[ 0 ] = 3;
+ else if ( eOp == ocChose )
+ nJump[ 0 ] = MAXJUMPCOUNT + 1;
+ else
+ nJump[ 0 ] = 2;
pRet = new FormulaJumpToken( eOp, (short*)nJump );
}
break;
diff --git a/formula/source/core/resource/core_resource.src b/formula/source/core/resource/core_resource.src
index 8b0ee14..d21a737 100644
--- a/formula/source/core/resource/core_resource.src
+++ b/formula/source/core/resource/core_resource.src
@@ -24,6 +24,8 @@
Resource RID_STRLIST_FUNCTION_NAMES_ENGLISH_ODFF
{
String SC_OPCODE_IF { Text = "IF" ; };
+ String SC_OPCODE_IF_ERROR { Text = "IFERROR" ; };
+ String SC_OPCODE_IF_NA { Text = "IFNA" ; };
String SC_OPCODE_CHOSE { Text = "CHOOSE" ; };
String SC_OPCODE_OPEN { Text = "(" ; };
String SC_OPCODE_CLOSE { Text = ")" ; };
@@ -362,6 +364,8 @@ Resource RID_STRLIST_FUNCTION_NAMES_ENGLISH_ODFF
Resource RID_STRLIST_FUNCTION_NAMES_ENGLISH
{
String SC_OPCODE_IF { Text = "IF" ; };
+ String SC_OPCODE_IF_ERROR { Text = "IFERROR" ; };
+ String SC_OPCODE_IF_NA { Text = "IFNA" ; };
String SC_OPCODE_CHOSE { Text = "CHOOSE" ; };
String SC_OPCODE_OPEN { Text = "(" ; };
String SC_OPCODE_CLOSE { Text = ")" ; };
@@ -700,6 +704,14 @@ Resource RID_STRLIST_FUNCTION_NAMES
{
Text [ en-US ] = "IF" ;
};
+ String SC_OPCODE_IF_ERROR
+ {
+ Text [ en-US ] = "IFERROR" ;
+ };
+ String SC_OPCODE_IF_NA
+ {
+ Text [ en-US ] = "IFNA" ;
+ };
String SC_OPCODE_CHOSE
{
Text [ en-US ] = "CHOOSE" ;
diff --git a/sc/inc/helpids.h b/sc/inc/helpids.h
index 2e2aa4c..e273ce4 100644
--- a/sc/inc/helpids.h
+++ b/sc/inc/helpids.h
@@ -433,6 +433,8 @@
#define HID_FUNC_NICHT "SC_HID_FUNC_NICHT"
#define HID_FUNC_WAHR "SC_HID_FUNC_WAHR"
#define HID_FUNC_WENN "SC_HID_FUNC_WENN"
+#define HID_FUNC_IFERROR "SC_HID_FUNC_IFERROR"
+#define HID_FUNC_IFNA "SC_HID_FUNC_IFNA"
#define HID_FUNC_ODER "SC_HID_FUNC_ODER"
#define HID_FUNC_UND "SC_HID_FUNC_UND"
#define HID_FUNC_XOR "SC_HID_FUNC_XOR"
diff --git a/sc/qa/unit/ucalc.cxx b/sc/qa/unit/ucalc.cxx
index d9d643f..14d0da1 100644
--- a/sc/qa/unit/ucalc.cxx
+++ b/sc/qa/unit/ucalc.cxx
@@ -671,6 +671,79 @@ void testFuncCOUNTIF(ScDocument* pDoc)
CPPUNIT_ASSERT_MESSAGE("We shouldn't count empty string as valid number.", result == 0.0);
}
+void testFuncIFERROR(ScDocument* pDoc)
+{
+ // IFERROR/IFNA (fdo#56124)
+
+ // Empty A1:A39 first.
+ clearRange(pDoc, ScRange(0, 0, 0, 0, 40, 0));
+
+ // Raw data (rows 1 through 12)
+ const char* aData[] = {
+ "1",
+ "e",
+ "=SQRT(4)",
+ "=SQRT(-2)",
+ "=A4",
+ "=1/0",
+ "=NA()",
+ "bar",
+ "4",
+ "gee",
+ "=1/0",
+ "23"
+ };
+
+ SCROW nRows = SAL_N_ELEMENTS(aData);
+ for (SCROW i = 0; i < nRows; ++i)
+ pDoc->SetString(0, i, 0, rtl::OUString::createFromAscii(aData[i]));
+
+ printRange(pDoc, ScRange(0, 0, 0, 0, nRows-1, 0), "data range for IFERROR/IFNA");
+
+ // formulas and results
+ struct {
+ const char* pFormula; const char* pResult;
+ } aChecks[] = {
+ { "=IFERROR(A1;9)", "1" },
+ { "=IFERROR(A2;9)", "e" },
+ { "=IFERROR(A3;9)", "2" },
+ { "=IFERROR(A4;-7)", "-7" },
+ { "=IFERROR(A5;-7)", "-7" },
+ { "=IFERROR(A6;-7)", "-7" },
+ { "=IFERROR(A7;-7)", "-7" },
+ { "=IFNA(A6;9)", "#DIV/0!" },
+ { "=IFNA(A7;-7)", "-7" },
+ { "=IFNA(VLOOKUP(\"4\";A8:A10;1;0);-2)", "4" },
+ { "=IFNA(VLOOKUP(\"fop\";A8:A10;1;0);-2)", "-2" },
+ { "{=IFERROR(3*A11:A12;1998)}[0]", "1998" }, // um.. this is not the correct way to insert a
+ { "{=IFERROR(3*A11:A12;1998)}[1]", "69" } // matrix formula, just a place holder, see below
+ };
+
+ nRows = SAL_N_ELEMENTS(aChecks);
+ for (SCROW i = 0; i < nRows-2; ++i)
+ {
+ SCROW nRow = 20 + i;
+ pDoc->SetString(0, nRow, 0, rtl::OUString::createFromAscii(aChecks[i].pFormula));
+ }
+
+ // Create a matrix range in last two rows of the range above, actual data
+ // of the placeholders.
+ ScMarkData aMark;
+ aMark.SelectOneTable(0);
+ pDoc->InsertMatrixFormula(0, 20 + nRows-2, 0, 20 + nRows-1, aMark, "=IFERROR(3*A11:A12;1998)", NULL);
+
+ pDoc->CalcAll();
+
+ for (SCROW i = 0; i < nRows; ++i)
+ {
+ rtl::OUString aResult;
+ SCROW nRow = 20 + i;
+ pDoc->GetString(0, nRow, 0, aResult);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ aChecks[i].pFormula, OUString::createFromAscii( aChecks[i].pResult), aResult);
+ }
+}
+
void testFuncVLOOKUP(ScDocument* pDoc)
{
// VLOOKUP
@@ -1063,6 +1136,7 @@ void Test::testCellFunctions()
testFuncPRODUCT(m_pDoc);
testFuncN(m_pDoc);
testFuncCOUNTIF(m_pDoc);
+ testFuncIFERROR(m_pDoc);
testFuncVLOOKUP(m_pDoc);
testFuncMATCH(m_pDoc);
testFuncCELL(m_pDoc);
@@ -4175,6 +4249,8 @@ void Test::testFunctionLists()
"AND",
"FALSE",
"IF",
+ "IFERROR",
+ "IFNA",
"NOT",
"OR",
"TRUE",
diff --git a/sc/source/core/inc/interpre.hxx b/sc/source/core/inc/interpre.hxx
index 762b818..267e659 100644
--- a/sc/source/core/inc/interpre.hxx
+++ b/sc/source/core/inc/interpre.hxx
@@ -368,6 +368,7 @@ bool LookupQueryWithCache( ScAddress & o_rResultPos,
const ScQueryParam & rParam ) const;
void ScIfJump();
+void ScIfError( bool bNAonly );
void ScChoseJump();
// Be sure to only call this if pStack[sp-nStackLevel] really contains a
diff --git a/sc/source/core/tool/interpr1.cxx b/sc/source/core/tool/interpr1.cxx
index ec85a01..0ef12c7 100644
--- a/sc/source/core/tool/interpr1.cxx
+++ b/sc/source/core/tool/interpr1.cxx
@@ -54,6 +54,7 @@
#include <math.h>
#include <vector>
#include <memory>
+#include <limits>
#include "cellkeytranslator.hxx"
#include "lookupcache.hxx"
#include "rangenam.hxx"
@@ -243,6 +244,188 @@ void ScInterpreter::ScIfJump()
}
+/** Store a matrix value in another matrix in the context of that other matrix
+ is the result matrix of a jump matrix. All arguments must be valid and are
+ not checked. */
+static void lcl_storeJumpMatResult( const ScMatrix* pMat, ScMatrix* pResMat, SCSIZE nC, SCSIZE nR )
+{
+ if ( pMat->IsValue( nC, nR ) )
+ {
+ double fVal = pMat->GetDouble( nC, nR );
+ pResMat->PutDouble( fVal, nC, nR );
+ }
+ else if ( pMat->IsEmpty( nC, nR ) )
+ {
+ pResMat->PutEmpty( nC, nR );
+ }
+ else
+ {
+ const String& rStr = pMat->GetString( nC, nR );
+ pResMat->PutString( rStr, nC, nR );
+ }
+}
+
+
+void ScInterpreter::ScIfError( bool bNAonly )
+{
+ RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "sc", "Donkers/erAck", "ScInterpreter::ScIfError" );
+ const short* pJump = pCur->GetJump();
+ short nJumpCount = pJump[ 0 ];
+ if (!sp)
+ {
+ PushError( errUnknownStackVariable);
+ aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
+ return;
+ }
+
+ FormulaTokenRef xToken( pStack[ sp - 1 ] );
+ bool bError = false;
+ sal_uInt16 nOldGlobalError = nGlobalError;
+ nGlobalError = 0;
+
+ MatrixDoubleRefToMatrix();
+ switch (GetStackType())
+ {
+ default:
+ Pop();
+ break;
+ case svError:
+ PopError();
+ bError = true;
+ break;
+ case svDoubleRef:
+ case svSingleRef:
+ {
+ ScAddress aAdr;
+ if (!PopDoubleRefOrSingleRef( aAdr))
+ bError = true;
+ else
+ {
+ ScBaseCell* pCell = GetCell( aAdr);
+ nGlobalError = GetCellErrCode( pCell);
+ if (nGlobalError)
+ bError = true;
+ }
+ }
+ break;
+ case svExternalSingleRef:
+ case svExternalDoubleRef:
+ {
+ double fVal;
+ String aStr;
+ // Handles also existing jump matrix case and sets error on
+ // elements.
+ GetDoubleOrStringFromMatrix( fVal, aStr);
+ if (nGlobalError)
+ bError = true;
+ }
+ break;
+ case svMatrix:
+ {
+ const ScMatrixRef pMat = PopMatrix();
+ if (!pMat || (nGlobalError && (!bNAonly || nGlobalError == NOTAVAILABLE)))
+ {
+ bError = true;
+ break; // switch
+ }
+ // If the matrix has no queried error at all we can simply use
+ // it as result and don't need to bother with jump matrix.
+ SCSIZE nErrorCol = ::std::numeric_limits<SCSIZE>::max(),
+ nErrorRow = ::std::numeric_limits<SCSIZE>::max();
+ SCSIZE nCols, nRows;
+ pMat->GetDimensions( nCols, nRows );
+ if (nCols == 0 || nRows == 0)
+ {
+ bError = true;
+ break; // switch
+ }
+ for (SCSIZE nC=0; nC < nCols && !bError; ++nC)
+ {
+ for (SCSIZE nR=0; nR < nRows && !bError; ++nR)
+ {
+ sal_uInt16 nErr = pMat->GetError( nC, nR );
+ if (nErr && (!bNAonly || nErr == NOTAVAILABLE))
+ {
+ bError = true;
+ nErrorCol = nC;
+ nErrorRow = nR;
+ }
+ }
+ }
+ if (!bError)
+ break; // switch, we're done and have the result
+
+ FormulaTokenRef xNew;
+ ScTokenMatrixMap::const_iterator aMapIter;
+ if (pTokenMatrixMap && ((aMapIter = pTokenMatrixMap->find( pCur)) != pTokenMatrixMap->end()))
+ {
+ xNew = (*aMapIter).second;
+ }
+ else
+ {
+ const ScMatrix* pMatPtr = pMat.get();
+ ScJumpMatrix* pJumpMat = new ScJumpMatrix( nCols, nRows );
+ ScMatrix* pResMatPtr = pJumpMat->GetResultMatrix();
+ // Init all jumps to no error to save single calls. Error
+ // is the exceptional condition.
+ const double fFlagResult = CreateDoubleError( errJumpMatHasResult);
+ pJumpMat->SetAllJumps( fFlagResult, pJump[ nJumpCount ], pJump[ nJumpCount ] );
+ // Up to first error position simply store results, no need
+ // to evaluate error conditions again.
+ SCSIZE nC = 0, nR = 0;
+ for ( ; nC < nCols && (nC != nErrorCol || nR != nErrorRow); /*nop*/ )
+ {
+ for ( ; nR < nRows && (nC != nErrorCol || nR != nErrorRow); ++nR)
+ {
+ lcl_storeJumpMatResult( pMatPtr, pResMatPtr, nC, nR);
+ }
+ if (nC != nErrorCol || nR != nErrorRow)
+ ++nC;
+ }
+ // Now the mixed cases.
+ for ( ; nC < nCols; ++nC)
+ {
+ for ( ; nR < nRows; ++nR)
+ {
+ sal_uInt16 nErr = pMat->GetError( nC, nR );
+ if (nErr && (!bNAonly || nErr == NOTAVAILABLE))
+ { // TRUE, THEN path
+ pJumpMat->SetJump( nC, nR, 1.0, pJump[ 1 ], pJump[ nJumpCount ] );
+ }
+ else
+ { // FALSE, EMPTY path, store result instead
+ lcl_storeJumpMatResult( pMatPtr, pResMatPtr, nC, nR);
+ }
+ }
+ }
+ xNew = new ScJumpMatrixToken( pJumpMat );
+ GetTokenMatrixMap().insert( ScTokenMatrixMap::value_type( pCur, xNew ));
+ }
+ nGlobalError = nOldGlobalError;
+ PushTempToken( xNew.get() );
+ // set endpoint of path for main code line
+ aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
+ return;
+ }
+ break;
+ }
+
+ if (bError && (!bNAonly || nGlobalError == NOTAVAILABLE))
+ {
+ // error, calculate 2nd argument
+ nGlobalError = 0;
+ aCode.Jump( pJump[ 1 ], pJump[ nJumpCount ] );
+ }
+ else
+ {
+ // no error, push 1st argument and continue
+ nGlobalError = nOldGlobalError;
+ PushTempToken( xToken.get());
+ aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] );
+ }
+}
+
+
void ScInterpreter::ScChoseJump()
{
RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "sc", "er", "ScInterpreter::ScChoseJump" );
@@ -560,18 +743,7 @@ bool ScInterpreter::JumpMatrix( short nStackLevel )
}
else
{
- if ( pMat->IsValue( nC, nR ) )
- {
- fVal = pMat->GetDouble( nC, nR );
- pResMat->PutDouble( fVal, nC, nR );
- }
- else if ( pMat->IsEmpty( nC, nR ) )
- pResMat->PutEmpty( nC, nR );
- else
- {
- const String& rStr = pMat->GetString( nC, nR );
- pResMat->PutString( rStr, nC, nR );
- }
+ lcl_storeJumpMatResult( pMat.get(), pResMat.get(), nC, nR);
}
lcl_AdjustJumpMatrix( pJumpMatrix, pResMat, nCols, nRows );
}
@@ -602,7 +774,7 @@ bool ScInterpreter::JumpMatrix( short nStackLevel )
pJumpMatrix->GetJump( nC, nR, fBool, nStart, nNext, nStop );
while ( bCont && nStart == nNext )
{ // push all results that have no jump path
- if ( pResMat )
+ if ( pResMat && (GetDoubleErrorValue( fBool) != errJumpMatHasResult) )
{
// a false without path results in an empty path value
if ( fBool == 0.0 )
diff --git a/sc/source/core/tool/interpr4.cxx b/sc/source/core/tool/interpr4.cxx
index 7155445..2f3b5ac 100644
--- a/sc/source/core/tool/interpr4.cxx
+++ b/sc/source/core/tool/interpr4.cxx
@@ -2712,7 +2712,7 @@ void ScInterpreter::ScExternal()
}
else if ( ( aUnoName = ScGlobal::GetAddInCollection()->FindFunction(aFuncName, false) ).Len() )
{
- // bLocalFirst=false in FindFunction, cFunc should be the stored
+ // bLocalFirst=false in FindFunction, cFunc should be the stored
// internal name
ScUnoAddInCall aCall( *ScGlobal::GetAddInCollection(), aUnoName, nParamCount );
@@ -3815,7 +3815,8 @@ StackVar ScInterpreter::Interpret()
// RPN code push without error
PushWithoutError( (FormulaToken&) *pCur );
}
- else if (pTokenMatrixMap && !(eOp == ocIf || eOp == ocChose) &&
+ else if (pTokenMatrixMap &&
+ !(eOp == ocIf || eOp == ocIfError || eOp == ocIfNA || eOp == ocChose) &&
((aTokenMatrixMapIter = pTokenMatrixMap->find( pCur)) !=
pTokenMatrixMap->end()) &&
(*aTokenMatrixMapIter).second->GetType() != svJumpMatrix)
@@ -3836,7 +3837,7 @@ StackVar ScInterpreter::Interpret()
nFuncFmtType = NUMBERFORMAT_NUMBER;
nFuncFmtIndex = 0;
- if ( eOp == ocIf || eOp == ocChose )
+ if ( eOp == ocIf || eOp == ocChose || eOp == ocIfError || eOp == ocIfNA )
nStackBase = sp; // don't mess around with the jumps
else
{
@@ -3863,6 +3864,8 @@ StackVar ScInterpreter::Interpret()
case ocDBArea : ScDBArea(); break;
case ocColRowNameAuto : ScColRowNameAuto(); break;
case ocIf : ScIfJump(); break;
+ case ocIfError : ScIfError( false ); break;
+ case ocIfNA : ScIfError( true ); break;
case ocChose : ScChoseJump(); break;
case ocAdd : ScAdd(); break;
case ocSub : ScSub(); break;
@@ -4258,7 +4261,9 @@ StackVar ScInterpreter::Interpret()
case ocIsString : \
case ocIsValue : \
case ocN : \
- case ocType :
+ case ocType : \
+ case ocIfError : \
+ case ocIfNA :
switch ( eOp )
{
diff --git a/sc/source/core/tool/parclass.cxx b/sc/source/core/tool/parclass.cxx
index ebdbb4a..6f3cae07 100644
--- a/sc/source/core/tool/parclass.cxx
+++ b/sc/source/core/tool/parclass.cxx
@@ -55,6 +55,8 @@ const ScParameterClassification::RawData ScParameterClassification::pRawData[] =
// created inside those functions and ConvertMatrixParameters() is not
// called for them.
{ ocIf, {{ Array, Reference, Reference }, 0 }},
+ { ocIfError, {{ Array, Reference }, 0 }},
+ { ocIfNA, {{ Array, Reference }, 0 }},
{ ocChose, {{ Array, Reference }, 1 }},
// Other specials.
{ ocOpen, {{ Bounds }, 0 }},
@@ -495,6 +497,8 @@ void ScParameterClassification::GenerateDocumentation()
case ocIf:
aToken.SetByte(3);
break;
+ case ocIfError:
+ case ocIfNA:
case ocChose:
aToken.SetByte(2);
break;
diff --git a/sc/source/core/tool/token.cxx b/sc/source/core/tool/token.cxx
index 539cad0..4ea3d4e 100644
--- a/sc/source/core/tool/token.cxx
+++ b/sc/source/core/tool/token.cxx
@@ -129,6 +129,11 @@ void ScRawToken::SetOpCode( OpCode e )
eType = svJump;
nJump[ 0 ] = 3; // If, Else, Behind
break;
+ case ocIfError:
+ case ocIfNA:
+ eType = svJump;
+ nJump[ 0 ] = 2; // If, Behind
+ break;
case ocChose:
eType = svJump;
nJump[ 0 ] = MAXJUMPCOUNT+1;
diff --git a/sc/source/filter/excel/xlformula.cxx b/sc/source/filter/excel/xlformula.cxx
index 58b3aa9..0cee777 100644
--- a/sc/source/filter/excel/xlformula.cxx
+++ b/sc/source/filter/excel/xlformula.cxx
@@ -365,7 +365,16 @@ static const XclFunctionInfo saFuncTable_Oox[] =
{ ocAverageIf, NOID, 2, 3, V, { RO, VR, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "AVERAGEIF" ) },
{ ocAverageIf, 255, 3, 4, V, { RO_E, RO, VR, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "AVERAGEIF" ) },
{ ocAverageIfs, NOID, 3, MX, V, { RO, RO, VR }, EXC_FUNCFLAG_IMPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "AVERAGEIFS" ) },
- { ocAverageIfs, 255, 4, MX, V, { RO_E, RO, RO, VR }, EXC_FUNCFLAG_EXPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "AVERAGEIFS" ) }
+ { ocAverageIfs, 255, 4, MX, V, { RO_E, RO, RO, VR }, EXC_FUNCFLAG_EXPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "AVERAGEIFS" ) },
+ { ocIfError, NOID, 2, 2, V, { VO, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "IFERROR" ) },
+ { ocIfError, 255, 3, 3, V, { RO_E, VO, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "IFERROR" ) }
+};
+
+/** Functions new in Excel 2013. */
+static const XclFunctionInfo saFuncTable_2013[] =
+{
+ { ocIfNA, NOID, 2, 2, V, { VO, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "IFNA" ) },
+ { ocIfNA, 255, 3, 3, V, { RO_E, VO, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "IFNA" ) }
};
#define EXC_FUNCENTRY_ODF( opcode, minparam, maxparam, flags, asciiname ) \
@@ -393,7 +402,6 @@ static const XclFunctionInfo saFuncTable_Odf[] =
EXC_FUNCENTRY_ODF( ocFormula, 1, 1, 0, "FORMULA" ),
EXC_FUNCENTRY_ODF( ocGamma, 1, 1, 0, "GAMMA" ),
EXC_FUNCENTRY_ODF( ocGauss, 1, 1, 0, "GAUSS" ),
- EXC_FUNCENTRY_ODF( ocNoName, 2, 2, 0, "IFNA" ),
EXC_FUNCENTRY_ODF( ocIsFormula, 1, 1, 0, "ISFORMULA" ),
EXC_FUNCENTRY_ODF( ocWeek, 1, 2, 0, "ISOWEEKNUM" ),
EXC_FUNCENTRY_ODF( ocMatrixUnit, 1, 1, 0, "MUNIT" ),
@@ -434,6 +442,7 @@ XclFunctionProvider::XclFunctionProvider( const XclRoot& rRoot )
if( eBiff >= EXC_BIFF8 )
(this->*pFillFunc)( saFuncTable_8, STATIC_ARRAY_END( saFuncTable_8 ) );
(this->*pFillFunc)( saFuncTable_Oox, STATIC_ARRAY_END( saFuncTable_Oox ) );
+ (this->*pFillFunc)( saFuncTable_2013, STATIC_ARRAY_END( saFuncTable_2013 ) );
(this->*pFillFunc)( saFuncTable_Odf, STATIC_ARRAY_END( saFuncTable_Odf ) );
}
diff --git a/sc/source/filter/oox/formulabase.cxx b/sc/source/filter/oox/formulabase.cxx
index a6f7e453..90470ab 100644
--- a/sc/source/filter/oox/formulabase.cxx
+++ b/sc/source/filter/oox/formulabase.cxx
@@ -711,13 +711,20 @@ static const FunctionData saFuncTableOox[] =
{ 0, "CUBEKPIMEMBER", 477, NOID, 3, 4, V, { VR }, 0 },
{ 0, "CUBESET", 478, NOID, 2, 5, V, { VR, RX, VR }, 0 },
{ 0, "CUBESETCOUNT", 479, NOID, 1, 1, V, { VR }, 0 },
- { 0, "IFERROR", 480, NOID, 2, 2, V, { VO, RO }, 0 },
+ { "IFERROR", "IFERROR", 480, NOID, 2, 2, V, { VO, RO }, FUNCFLAG_MACROCALL },
{ "COUNTIFS", "COUNTIFS", 481, NOID, 2, MX, V, { RO, VR }, FUNCFLAG_MACROCALL | FUNCFLAG_PARAMPAIRS },
{ "SUMIFS", "SUMIFS", 482, NOID, 3, MX, V, { RO, RO, VR }, FUNCFLAG_MACROCALL | FUNCFLAG_PARAMPAIRS },
{ "AVERAGEIF", "AVERAGEIF", 483, NOID, 2, 3, V, { RO, VR, RO }, FUNCFLAG_MACROCALL },
{ "AVERAGEIFS", "AVERAGEIFS", 484, NOID, 3, MX, V, { RO, RO, VR }, FUNCFLAG_MACROCALL | FUNCFLAG_PARAMPAIRS }
};
+/** Functions new in Excel 2013. */
+/* FIXME: BIFF12 function identifer available? Where to obtain? */
+static const FunctionData saFuncTable2013[] =
+{
+ { "IFNA", "IFNA", NOID, NOID, 2, 2, V, { VO, RO }, FUNCFLAG_MACROCALL },
+};
+
/** Functions defined by OpenFormula, but not supported by Calc or by Excel. */
static const FunctionData saFuncTableOdf[] =
{
@@ -739,7 +746,6 @@ static const FunctionData saFuncTableOdf[] =
{ "FORMULA", 0, NOID, NOID, 1, 1, V, { RO }, FUNCFLAG_MACROCALLODF },
{ "GAMMA", 0, NOID, NOID, 1, 1, V, { VR }, FUNCFLAG_MACROCALLODF },
{ "GAUSS", 0, NOID, NOID, 1, 1, V, { VR }, FUNCFLAG_MACROCALLODF },
- { "IFNA", 0, NOID, NOID, 2, 2, V, { VR, RO }, FUNCFLAG_MACROCALLODF },
{ "ISFORMULA", 0, NOID, NOID, 1, 1, V, { RO }, FUNCFLAG_MACROCALLODF },
{ "ISOWEEKNUM", 0, NOID, NOID, 1, 2, V, { VR }, FUNCFLAG_MACROCALLODF },
{ "MUNIT", 0, NOID, NOID, 1, 1, A, { VR }, FUNCFLAG_MACROCALLODF },
@@ -866,6 +872,7 @@ FunctionProviderImpl::FunctionProviderImpl( FilterType eFilter, BiffType eBiff,
if( eBiff >= BIFF8 )
initFuncs( saFuncTableBiff8, STATIC_ARRAY_END( saFuncTableBiff8 ), nMaxParam, bImportFilter );
initFuncs( saFuncTableOox, STATIC_ARRAY_END( saFuncTableOox ), nMaxParam, bImportFilter );
+ initFuncs( saFuncTable2013, STATIC_ARRAY_END( saFuncTable2013 ), nMaxParam, bImportFilter );
initFuncs( saFuncTableOdf, STATIC_ARRAY_END( saFuncTableOdf ), nMaxParam, bImportFilter );
}
diff --git a/sc/source/ui/src/scfuncs.src b/sc/source/ui/src/scfuncs.src
index 1b68b2e..ce354e6 100644
--- a/sc/source/ui/src/scfuncs.src
+++ b/sc/source/ui/src/scfuncs.src
@@ -2651,6 +2651,70 @@ Resource RID_SC_FUNCTION_DESCRIPTIONS1
Text [ en-US ] = "The result of the function if the logical test returns FALSE." ;
};
};
+ // -=*# Resource for function IFERROR #*=-
+ Resource SC_OPCODE_IF_ERROR
+ {
+ String 1 // Description
+ {
+ Text [ en-US ] = "Returns value if not an error value, else alternative." ;
+ };
+ ExtraData =
+ {
+ 0;
+ ID_FUNCTION_GRP_LOGIC;
+ U2S( HID_FUNC_IFERROR );
+ 2; 0; 0;
+ 0;
+ };
+ String 2 // Name of Parameter 1
+ {
+ Text [ en-US ] = "value" ;
+ };
+ String 3 // Description of Parameter 1
+ {
+ Text [ en-US ] = "The value to be calculated." ;
+ };
+ String 4 // Name of Parameter 2
+ {
+ Text [ en-US ] = "alternative value" ;
+ };
+ String 5 // Description of Parameter 2
+ {
+ Text [ en-US ] = "The alternative to be returned, should value be an error value." ;
+ };
+ };
+ // -=*# Resource for function IFNA #*=-
+ Resource SC_OPCODE_IF_NA
+ {
+ String 1 // Description
+ {
+ Text [ en-US ] = "Returns value if not a #N/A error, else alternative." ;
+ };
+ ExtraData =
+ {
+ 0;
+ ID_FUNCTION_GRP_LOGIC;
+ U2S( HID_FUNC_IFNA );
+ 2; 0; 0;
+ 0;
+ };
+ String 2 // Name of Parameter 1
+ {
+ Text [ en-US ] = "value" ;
+ };
+ String 3 // Description of Parameter 1
+ {
+ Text [ en-US ] = "The value to be calculated." ;
+ };
+ String 4 // Name of Parameter 2
+ {
+ Text [ en-US ] = "alternative value" ;
+ };
+ String 5 // Description of Parameter 2
+ {
+ Text [ en-US ] = "The alternative to be returned, should value be a #N/A error." ;
+ };
+ };
// -=*# Resource for function ODER #*=-
Resource SC_OPCODE_OR
{
diff --git a/sc/util/hidother.src b/sc/util/hidother.src
index d339229..718dce2 100644
--- a/sc/util/hidother.src
+++ b/sc/util/hidother.src
@@ -149,6 +149,8 @@ hidspecial HID_FUNC_FALSCH { HelpID = HID_FUNC_FALSCH; };
hidspecial HID_FUNC_NICHT { HelpID = HID_FUNC_NICHT; };
hidspecial HID_FUNC_WAHR { HelpID = HID_FUNC_WAHR; };
hidspecial HID_FUNC_WENN { HelpID = HID_FUNC_WENN; };
+hidspecial HID_FUNC_IFERROR { HelpID = HID_FUNC_IFERROR; };
+hidspecial HID_FUNC_IFNA { HelpID = HID_FUNC_IFNA; };
hidspecial HID_FUNC_ODER { HelpID = HID_FUNC_ODER; };
hidspecial HID_FUNC_UND { HelpID = HID_FUNC_UND; };
hidspecial HID_FUNC_XOR { HelpID = HID_FUNC_XOR; };
--------------erAck-patch-parts--
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 836 bytes
Desc: not available
URL: <http://lists.freedesktop.org/archives/libreoffice/attachments/20130112/8b9c1bee/attachment-0001.pgp>
More information about the LibreOffice
mailing list