EPPlus multiplying a range by a constant inside a VLookup function returning ValueError - excel

I am using the EPPlus library in my ASP.Net application.
What I am trying to do is open a spreadsheet, input some values into cells and then read another cell which contains the result of a calculation. The spreadsheet itself is confidential so I can't provide many details on it.
In order to get my calculations to work I have had to modify the source code for EPplus, changing the Compile function in the ExcelAddressExpression.cs file to ignore the ParentIsLookupFunction bool as shown at the bottom of the question.
so it was able to evaluate the term 5*$f$7
What I want to know is what situations is it useful to keep the CompileResult as an ExcelAddress, so I do not run into any incorrect calculations or errors in other parts of the spreadsheet.
For reference here are the steps I went though to get here:
My code is something like this
using (ExcelPackage p = new ExcelPackage(FilePath, true))
{
ExcelWorksheet ws = p.Workbook.Worksheets["Calculations"];
ws.Cells["b7"].Value = 50;
ws.Cells["f9"].Value = 500000;
ws.Cells["j216"].Calculate();
string result = ws.Cells["j216"].Value.ToString();
}
The formula in cell J216 is
=VLOOKUP($B$7+$F$221+$K$13-$F$8-1,Sheet2!$A$4:$T$103,5*$F$7+2*$B$8+$B$9-5,FALSE)
and the result I got was '#VALUE!'
I have attached a log file and found the issue is in the VLookup function
Worksheet: Calculations
Address: J216
OfficeOpenXml.FormulaParsing.Exceptions.ExcelErrorValueException: #VALUE!
at OfficeOpenXml.FormulaParsing.Excel.Functions.IntArgumentParser.Parse(Object obj)
at OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup.LookupArguments..ctor(IEnumerable`1 arguments, ArgumentParsers argumentParsers, ParsingContext context)
at OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup.VLookup.Execute(IEnumerable`1 arguments, ParsingContext context)
at OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers.LookupFunctionCompiler.Compile(IEnumerable`1 children, ParsingContext context)
at OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionExpression.Compile()
The next step I took was to download the source code for EPPlus, and debug through the code as it executed, eventually finding the problem was at line 165 of Operator.cs
l = l ?? new CompileResult(0, DataType.Integer);
r = r ?? new CompileResult(0, DataType.Integer);
if (l.DataType == DataType.Integer && r.DataType == DataType.Integer)
{
return new CompileResult(l.ResultNumeric*r.ResultNumeric, DataType.Integer);
}
else if ((l.IsNumeric || l.IsNumericString || l.IsDateString || l.Result is ExcelDataProvider.IRangeInfo) &&
(r.IsNumeric || r.IsNumericString || r.IsDateString || r.Result is ExcelDataProvider.IRangeInfo))
{
return new CompileResult(l.ResultNumeric*r.ResultNumeric, DataType.Decimal);
}
return new CompileResult(eErrorType.Value);
When evaluating the equation 5*$F$7, the second parameter was of DataType ExcelAddress which is results in a compile result exception being thrown.
The root cause of this is in the Compile function of the ExcelAddressExpression.cs file the ParentIsLookupFunction boolean controls whether the cell is evaluated or left as an address.
public override CompileResult Compile()
{
if (ParentIsLookupFunction)
{
return new CompileResult(ExpressionString, DataType.ExcelAddress);
}
else
{
return CompileRangeValues();
}
}
I have modified my version of the code to simply be
public override CompileResult Compile()
{
return CompileRangeValues();
}
As said at the top of the question, what I want to know is why would you want to return the ExcelAddress CompileResult, It was obviously put there for a reason and I do not want to break some other calculations in my spreadsheet.
I can confirm though that for at least this calculation it is now working correctly.

I wrote the formula engine of EPPlus some years ago, but don't work much on the project these days. If I recall it correctly there are cases where you don't want to compile the excel address in the expression, but rather pass it on to the executing function. Have you tried to run the unit tests? The formula engine is covered by hundreds of tests and the test result could provide some guidance.

I was able to answer this after experimenting with different spreadsheets.
Returning the value as an ExcelAddress is useful in the event that the address is not operated on like in my earlier example 5*$F$7 such as an IF statement inside a VLookup.
In the event that there is an operator you will want to return the contents of the cell.
I modified the EPPlus code to now check for any operators separating terms in the formula
public override CompileResult Compile()
{
if (ParentIsLookupFunction &&
Operator == null &&
(Prev == null || Prev.Operator == null))
{
return new CompileResult(ExpressionString, DataType.ExcelAddress);
}
else
{
return CompileRangeValues();
}
}

Related

Apache poi 5.2.2 string cell type

In the documentation, from apache poi 5 cell.setCellType is deprecated, and according to the documentation, the library will handle different types itself, but i have the strange thing that is happening. When i try to put a string into a cell i always have (after i open the excel) a number type inside of the cell it instead text, no matter which implementation of workbook i`m using. I have tried with all available and the result is the same. So i took a look of implementation of XSSFCell(because currently i use XSSFWordBook) and i saw the following:
CellType cellType = getCellType();
if (cellType == CellType.FORMULA) {
_cell.setV(str.getString());
_cell.setT(STCellType.STR);
} else {
if(_cell.getT() == STCellType.INLINE_STR) {
//set the 'pre-evaluated result
_cell.setV(str.getString());
} else {
_cell.setT(STCellType.S);
XSSFRichTextString rt = (XSSFRichTextString)str;
rt.setStylesTableReference(_stylesSource);
int sRef = _sharedStringSource.addSharedStringItem(rt);
_cell.setV(Integer.toString(sRef));
}
}
so it always convert the text into "RichTextString" object, and after that it is entering
in the else condition. Can someone explain me how i can put the type of the cell to be
STCellType.INLINE_STR
in order to avoid this converting to Integer.
P.S. keep in mind that also tried to use the deprecated
setCellType
and again it is converted to integer or it is entering in the else code block

Writing if conditions in Excel

I am writing some if conditions in excel and i don`t succeed. I would glad if you can help me.
I want to write the following if condition(Pseudo code):
If(L28 appears between C44:C47)
{
Value = D31
}
else if( L28 ==C48)
{
Value = D32
}
If(L28 appears between C49:C53)
{
Value = D30
}
else If(L28 appears between C54:C57)
{
Value = D29
}
else
{
Value = L28
}
I have written the following part of code, but it is does not work.
"=IF(COUNTIF(C44:C47,L28),D31,if(L28=C48,D32,if(COUNTIF(C49:C53,L28),D30,if(COUNTIF(C54:C57,L28),D29))))"
Well, try this, but I have not tested it:
=if(iferror(match(L28,C44:C47,0),0)>0,D31,if(L28=C48,D32,if(iferror(match(L28,CC49:C53,0),0)>0,D30,if(iferror(match(L28,C54:C57,0),0)>0,D29,L28))))
Excel has a new ifs() function in one of the latest updates.
ifs(condition1, value1, condition2, value2,...) outputs the value for the first condition that is true.
that could simplify the formula a bit. No more need for nested if(). Below is Solar Mikes solution with ifs().
=ifs(iferror(match(L28,C44:C47,0),0)>0,D31,L28=C48,D32,iferror(match(L28,CC49:C53,0),0)>0,D30,iferror(match(L28,C54:C57,0),0>0,D29,L28)

I have a ComboBox control, no response at first edit and works at second time

Now I get a ComboBox control, everything is OK except that:
firstly, I selected one in a multiple-selection list box, works;
secondly, I edit the selected text, no update,
thirdly, I edit again then changed as I wanted.
At my program, every time selecting and editing would call a function named ChangeControlNotify(ClistControl* pControl) and there is a GetTest() in this function. The problem occurred at that function GetTest() because it calls CComboBox::GetCurSel. GetCurSel function should return -1 at first time edit, but it returns the selecting line number as that firstly selecting.
Does anyone know why? I think maybe there is still a handler reminding after firstly editing. But I don't know and have no idea yet. If some kindly guys help. very aprreciate. Thanks
_bstr_t CComboBoxListControl::GetText()
{
CComBSTR bstr;
int nSel = m_comboBox.GetCurSel();
if ((-1 == nSel) && ((m_comboBox.GetWindowLong(GWL_STYLE) & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST))
{
m_comboBox.GetWindowText(&bstr);
}
else
{
m_comboBox.GetLBTextBSTR(nSel, bstr.m_str);
}
return bstr.m_str;
}
template <bool bIsCheckListView>
void CRichListControls<bIsCheckListView>::ChangeControlNotify(CListControl* pControl)
{
CListPosition lp = FindControlPosition(pControl);
SetItemText(lp.GetLine(), lp.GetColumn(), pControl->GetText(), false);
CRichListControlData data(m_hWnd, lp);
SendMessage(m_wndContained.m_hWnd,
WM_RICHLISTCONTROLS_MESSAGE,
WPARAM(rmtModify),
reinterpret_cast<LPARAM>(&data));
}

SpreadsheetGear SetArray of double when double.NaN is present

I have a simplified test scenario created where I have a spreadsheet with two cells (C2/C3) having an array formula:
{=NaNTest()}
My simplified CustomFunction is as follows:
public class NaNTest : CustomFunctions.Function
{
public NaNTest() : this( "NaNTest" ) { }
public NaNTest( string name ) : base( name, CustomFunctions.Volatility.Invariant, CustomFunctions.ValueType.Variant ) { }
public override void Evaluate( CustomFunctions.IArguments a, CustomFunctions.IValue r )
{
var result = new double[ 1, 2 ];
result[ 0, 0 ] = double.NaN;
result[ 0, 1 ] = 0d;
r.SetArray( result );
}
}
This sets both C2 and C3 to #NUM! when I'd expect only C2 to be. Is there a way to make it correctly* assign C3 to 0?
Thanks in advance.
* I say correctly because we have to implement an Excel add-in that our clients use to author spreadsheets and it provides same 'functionality' that we provide on 'our servers' when we open/process the spreadsheet in our 'SpreadsheetGear calculations' (i.e. the NaNTest() function above). The libraries we use to create the add-in only assign C2 to #NUM! and having the two implementations (client side add-in vs server side SpreadsheetGear) behaving differently makes maintenance/debugging difficult.
This behavior is by design. Note the comment in the documentation for the IValue.SetArray(...) method:
If any of the numbers in the array are not valid numbers, the result
of the custom function will be ValueError.Num.
Since NaN isn't a valid number the entire array will resolve to #NUM! instead. Actually, if you try to set a cell value on its out (outside of a custom function), such as...
worksheet.Cells["A1"].Value = double.NaN;
...you should find that cell evaluates to #NUM! as well. If such cases can occur in your custom function, you'll likely just need to write a check for this condition and respond in whatever manner is required by your application.

Using a Foreach without VBA - entering it as a formula

I can not find a formulat that multiplies chances the way i want.
I can not use additional cells or VBA because this is a company excel sheet that is locked to a certain format.
Here is some pseudocode that illustrates what i want to do;
value oneminus(value) //typeof delegate
{
return 1 - value;
}
value ProductDelegate(range, delegate)
{
Result result = 1;
foreach value in range
{
result *= delegate(value);
}
return result;
}
What i would want to call is a prefabricated version of the ProductDelegate. I would call it like so =ProductDelegate(J56:J73, "1-"&J). I do not think that ProductDelegate actually exists so i feel like what i am asking is not implemented in excel. Are there any options for this particular usecase? Any statistics function i am missing?
You don't need VBA for this. Just type the following formula but hold down CTRL-SHFT when hitting enter:
=PRODUCT(1-J56:J73)

Resources