Apache poi 5.2.2 string cell type - apache-poi

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

Related

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)

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

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();
}
}

apache poi read custom property

I have added a custom property to my workbook object like:
((XSSFWorkbook)workBook).getProperties().getCustomProperties().addProperty("fileNameSuffix" , "testName");
Now how can I read it back again.
Why there is no such method as getProperty(String key) ?
Do you mean like the method POIXMLProperties.CustomProperties.getProperty(String)? I think that should do what you want. Well, assuming you're on a new enough version of Apache POI to have it at least!
However, do note that it returns a CTProperty object, which is fairly low-level, and doesn't have an explicit type on it. You'll have to call the various isSetXXX methods to work out what kind it is, then getXXX to get the value.
There's an example of how to do that in POIXMLPropertiesTextExtractor
I figure a way to do it, but I do not really like it this way
List<CTProperty> customProperties = workBook.getProperties().getCustomProperties().getUnderlyingProperties().getPropertyList();
String fileNameSuffix = "";
for(int i = 0 ; i < customProperties.size() ; i++) {
CTProperty property = customProperties.get(i);
if (customProperties.get(i).getName().equals("testName"))
fileNameSuffix = property.getLpwstr(); // getLpwstr() will return the value of the property
}

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)

Adding detectable Nullable values to CsvHelper

I was wondering if CsvHelper by Josh Close has anything in the configuration I am missing to translate values to null. I am a huge fan of this library, but I always thought there should be some sort of configuration to let it know what values represent NULL in your file. An example would be a column with the value "NA", "EMPTY", "NULL", etc. I am sure I could create my own TypeConverter, but I was hoping there would be an easier option to set somewhere in a config as this tends to be fairly common with files I encounter.
Is there a configuration setting to do this relatively easily?
I found the TypeConversion in the CsvHelper.TypeConversion namespace but am not sure where to apply something like this or an example of the correct usage:
new NullableConverter(typeof(string)).ConvertFromString(new TypeConverterOptions(), "NA")
I am also using the latest version 2.2.2
Thank you!
I think some time in the last seven years and thirteen versions since this question was asked the options for doing this without a custom type map class expanded, e.g.:
csvReader.Context.TypeConverterOptionsCache.GetOptions<string>().NullValues.Add("NULL");
csvReader.Context.TypeConverterOptionsCache.GetOptions<DateTime?>().NullValues.AddRange(new[] { "NULL", "0" });
csvReader.Context.TypeConverterOptionsCache.GetOptions<int?>().NullValues.Add("NULL");
csvReader.Context.TypeConverterOptionsCache.GetOptions<bool>().BooleanFalseValues.Add("0");
csvReader.Context.TypeConverterOptionsCache.GetOptions<bool>().BooleanTrueValues.Add("1");
CsvHelper can absolutely handle nullable types. You do not need to roll your own TypeConverter if a blank column is considered null. For my examples I am assuming you are using user-defined fluent mappings.
The first thing you need to do is construct a CsvHelper.TypeConverter object for your Nullable types. Note that I'm going to use int since strings allow null values by default.
public class MyClassMap : CsvClassMap<MyClass>
{
public override CreateMap()
{
CsvHelper.TypeConversion.NullableConverter intNullableConverter = new CsvHelper.TypeConversion.NullableConverter(typeof(int?));
Map(m => m.number).Index(2).TypeConverter(intNullableConverter);
}
}
Next is setting the attribute on your CsvReader object to allow blank columns & auto-trim your fields. Personally like to do this by creating a CsvConfiguration object with all of my settings prior to constructing my CsvReader object.
CsvConfiguration csvConfig = new CsvConfiguration();
csvConfig.RegisterClassMap<MyClassMap>();
csvConfig.WillThrowOnMissingField = false;
csvConfig.TrimFields = true;
Then you can call myReader = new CsvReader(stream, csvConfig) to build the CsvReader object.
IF you need to have defined values for null such as "NA" == null then you will need to roll your own CsvHelper.TypeConversion class. I recommend that you extend the NullableConverter class to do this and override both the constructor and ConvertFromString method. Using blank values as null is really your best bet though.
I used "ConvertUsing"...
public class RecordMap : CsvHelper.Configuration.ClassMap<Record>
{
public RecordMap()
{
AutoMap();
Map(m => m.TransactionDate).ConvertUsing( NullDateTimeParser );
Map(m => m.DepositDate).ConvertUsing( NullDateTimeParser );
}
public DateTime? NullDateTimeParser(IReaderRow row)
{
//"CurrentIndex" is a bit of a misnomer here - it's the index of the LAST GetField call so we need to +1
//https://github.com/JoshClose/CsvHelper/issues/1168
var rawValue = row.GetField(row.Context.CurrentIndex+1);
if (rawValue == "NULL")
return null;
else
return DateTime.Parse(rawValue);
}
}

Resources