How to automatically remove # of the excel formula generated by POI setCellFormula - apache-poi

When use setCellFormula set by paramete "CHISQ.TEST(ChiSq_Data!D5:F5,ChiSq_Data!L5:N5)",but the output was "=#CHISQ.TEST(ChiSq_Data!D5:F5,ChiSq_Data!L5:N5)", the # symbol make the formula did not work and shows #VALUE in the result excel.
How can I remove the # automatically?

This is a similar problem as this one: Apache POI Excel Formula entering # Symbols where they don't belong.
All new functions (introduced after Excel 2007) are prefixed with _xlfn in internally file storage. The GUI does not show that prefix if the Excel version is able to interpret that function. If the Excel version is too old to be able to interpret that function you may see that prefix even in GUI.
Apache POI creates Excel files and that's why writes formulas in file storage directly. Using:
cell.setCellFormula("CHISQ.TEST(ChiSq_Data!D5:F5,ChiSq_Data!L5:N5)");
it writes CHISQ.TEST(ChiSq_Data!D5:F5,ChiSq_Data!L5:N5) into the file storage but the Excel GUI expects _xlfn.CHISQ.TEST(ChiSq_Data!D5:F5,ChiSq_Data!L5:N5). That's why the #NAME? error.
But why the #? The # is the implicit intersection operator. Implicit intersection is a new feature of Excel 365 (a silly one in my opinion, as well as dynamic array and spilling array behavior). And because Excel 365 does not know the function CHISQ.TEST without the prefix but it contains arrays of cells as parameters, it puts # in front of it to show that it would use implicit intersection if it would know it.
So the solution is to put the correct prefix before the function name in file storage to make it work:
cell.setCellFormula("_xlfn.CHISQ.TEST(ChiSq_Data!D5:F5,ChiSq_Data!L5:N5)");
Complete example to test:
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
class CreateExcelCHISQ_TEST {
public static void main(String[] args) throws Exception {
try (
Workbook workbook = new XSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xlsx") ) {
Sheet sheet = workbook.createSheet();
Row row;
Cell cell;
// Filling dummy data to another sheet
Sheet otherSheet = workbook.createSheet("ChiSq_Data");
row = otherSheet.createRow(4);
row.createCell(3).setCellValue(123);
row.createCell(4).setCellValue(456);
row.createCell(5).setCellValue(78);
row.createCell(11).setCellValue(122.5);
row.createCell(12).setCellValue(456.5);
row.createCell(13).setCellValue(77.5);
row = sheet.createRow(0);
cell = row.createCell(0);
//cell.setCellFormula("CHISQ.TEST(ChiSq_Data!D5:F5,ChiSq_Data!L5:N5)"); // wrong
cell.setCellFormula("_xlfn.CHISQ.TEST(ChiSq_Data!D5:F5,ChiSq_Data!L5:N5)");
workbook.write(fileout);
}
}
}

Related

Excel or POI changes my date format to something that doesn't work

I've got an Excel file I want to recreate through POI. The existing Excel file uses as DataFormat _(* #.##0_);_(* (#.##0);_(* "-"??_);_(#_). But when I assign that data format through POI, the resulting Excel file will instead use _(* #,##0_);_(* (#,##0);_(* "-"??_);_(#_). Note the tiny difference: a dot changed to a comma. Because of this, the entire format doesn't work anymore. It's not like it's now showing a comma where it used to have a dot; it's formatting the entire value in a completely different way.
Why does this happen? And how do I fix it?
The correct format string _(* #.##0_);_(* (#.##0);_(* "-"??_);_(#_) results in the number being displayed as 13.534.000.
The incorrect format string that Excel or POI changes it to, _(* #,##0_);_(* (#,##0);_(* "-"??_);_(#_) formats the value as 13534000,0.
It's a complete mystery to me why it would do that. I suppose it has something to do with the US and Europe using different formats to display big numbers, but I would imagine that that's exactly what this data format is supposed to address. Instead, it turns it into nonsense.
Apache POI creates Microsoft Office files. Those files never are localized. They always store data in en_US locale. The locale dependent adjustments are done in locale Office applications then. So a Microsoft Office file can be sent around the world without the need to change the stored data to a foreign locale.
So if you set the data format using ...
...
Workbook workbook = new XSSFWorkbook();
DataFormat dataformat = workbook.createDataFormat();
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setDataFormat(dataformat.getFormat("_(* #,##0_);_(* (#,##0);_(* \"-\"??_);_(#_)"));
...
... the format pattern always needs to be en_US. That means dot is decimal separator, comma is thousands delimiter. A locale Excel application might adjust that to _(* #.##0_);_(* (#.##0);_(* "-"??_);_(#_) then.
Let's have a complete example:
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class CreateExcelNumberFormat {
public static void main(String[] args) throws Exception {
Workbook workbook = new XSSFWorkbook();
DataFormat dataformat = workbook.createDataFormat();
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setDataFormat(dataformat.getFormat("_(* #,##0_);_(* (#,##0);_(* \"-\"??_);_(#_)"));
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue(1234567.89);
cell.setCellStyle(cellStyle);
row = sheet.createRow(1);
cell = row.createCell(0);
cell.setCellValue(-1234567.89);
cell.setCellStyle(cellStyle);
sheet.setColumnWidth(0, 15*256);
FileOutputStream out = new FileOutputStream("./CreateExcelNumberFormat.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
}
The result Excel file looks in my German Excel like so:

How do I emulate Excel's "Fill down" in Apache POI?

I've got a template workbook, with a sheet ("All data") which I populate using Apache POI. I don't know how many rows I'm going to need in "All data" when I start.
In another sheet (call it "Calc"), I have 4 columns containing formulae that do stuff based on "All data". I need to have as many rows in Calc as in "All data", and I thought the easiest way to do it would be to have, in the template, one row with the formulae in it, which I can then fill down the sheet as many times as necessary.
Thus, in the template I have:
Col1Header | Col2Header | Col3Header | Col4Header
=+'All data'!F2 | =IF(LEFT(A55,1)="4",'All data'!R2,"") | =IF(LEFT(A55,1)="4",'All data'!O2,"") | =+'All data'!W2
Then I would expect to be able to "fill down" from that first formula line, so that I have n rows (where n is the number of rows I'm using in the "All data" sheet).
However, I cannot see how to do "fill down" in Apache POI. Is it something that's not possible? Or am I looking for the wrong name?
Yes, an alternative method would be simply to change the template by manually copying down more rows than I would ever expect to be using, but that is (a) inelegant and (b) is asking for trouble in the future:-)
I feel sure there must be a better way?
If this is for an Office Open XML workbook (*.xlsx, XSSF) and current apache poi 5.0.0 is used, then XSSFSheet.copyRows can be used. The default CellCopyPolicy copies formulas and adjusts the cell references in them.
Example:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import java.io.FileInputStream;
import java.io.FileOutputStream;
class ExcelReadCopyRowsAndWrite {
public static void main(String[] args) throws Exception {
String fileIn= "./TestIn.xlsx";
String fileOut= "./TestOut.xlsx";
int n = 10; // 10 rows needed
int fillRowsFromIdx = 1; // start at row 2 (index 1) which is row having the formulas
int fillRowsToIdx = fillRowsFromIdx + n - 1;
try (Workbook workbook = WorkbookFactory.create(new FileInputStream(fileIn));
FileOutputStream out = new FileOutputStream(fileOut)) {
Sheet sheet = workbook.getSheet("Calc"); // do it in sheet named "Calc"
if (sheet instanceof XSSFSheet) {
XSSFSheet xssfSheet = (XSSFSheet) sheet;
for (int i = fillRowsFromIdx; i < fillRowsToIdx; i++) {
xssfSheet.copyRows(i, i, i+1, new CellCopyPolicy());
}
}
workbook.write(out);
}
}
}
ThecopyRows method is only in XSSF up to now. For an example how to copy formulas also working for BIFF workbook (*.xls, HSSF) see Apache POI update formula references when copying.

POI 3.17 creating Cell Comments in a cloned sheet creates inconsistent xlsx

I used the cloneSheet method to copy a sheet within the same workbook which already contains comments.
Afterwards new comments where added to this new sheet and the excel saved.
When openening the file with Excel 365, it complained about /xl/comments1.xml and recovered the file.
The newly created comments are available. The comments from the clone are removed during the recovery.
Opening the zip file and looking at the /xl/comments1.xml, it shows a difference.
Is this an issue with the cloneSheet method or is Microsoft using new ways?
As mature the apache poi project even is, it is long not finished yet. So one who needs using it must nevertheless know about the internals of the used file systems.
So how are comments stored in Excel's Office Open XML (*.xlsx) file system?
The whole file system is a ZIP archive. The sheet data of first sheet is in /xl/worksheets/sheet1.xml within that ZIP. The XML there has
...
<legacyDrawing r:id="rId2"/>
...
which points to a legacy VMLDrawing having rId2 in the relation parts XML of the first sheet.
The relation parts XML of the first sheet is /xl/worksheets/_rels/sheet1.xml.rels and looks like
<Relationships>
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="../comments1.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" Target="../drawings/vmlDrawing1.vml"/>
...
</Relationships>
So rId2 points to /xl/drawings/vmlDrawing1.vml and rId3 points to /xl/comments1.xml.
So the vmlDrawing1.vml contains the anchors of the comments' shapes on the sheet while the 'comments1.xml` contains the comments' contents.
Now what is method public XSSFSheet cloneSheet(int sheetNum, String newName) of XSSFWorkbook doing?
At first, it copies all sheet's relations. So also the relations to the VMLDrawing and the Comments parts are copied. So if we are cloning first sheet, then after cloning /xl/worksheets/_rels/sheet2.xml.rels will have the same content as /xl/worksheets/_rels/sheet1.xml.rels has.
But then it states: "Cloning sheets with comments is not yet supported." and removes the <legacyDrawing r:id="rId2"/> from the sheet's XML. But the previous copied relations are not removed.
So as the result we have a cloned sheet without comments linked in the sheet but having relations to the comments and their shapes set.
If we now creating new comments in that cloned sheet, then new /xl/drawings/vmlDrawing2.vml also is created inclusive its relation in /xl/worksheets/_rels/sheet2.xml.rels. So after that we have a /xl/worksheets/_rels/sheet2.xml.rels which points to /xl/drawings/vmlDrawing1.vml and to /xl/drawings/vmlDrawing2.vml as well. But that's not allowed and so Excel throws error while opening and suggests repairing.
Furthermore the new created comments are stored in /xl/comments1.xml which also is wrong because each sheet needs its own comments part. That happens because while cloning theXSSFSheet the field private CommentsTable sheetComments also is cloned and contains the old comments table of the source sheet now.
So for being able creating comments in the cloned sheet, we need get rid of the wrong relations and also get rid of the wrong CommentsTable in field sheetComments of the XSSFSheet.
Example:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.POIXMLDocumentPart.RelationPart;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
class ExcelCloneSheetHavingComments {
public static void main(String[] args) throws Exception {
Workbook workbook = WorkbookFactory.create(new FileInputStream("ExcelHavingComments.xlsx"));
Sheet sheetClone = workbook.cloneSheet(0);
workbook.setSheetName(workbook.getSheetIndex(sheetClone), "Cloned first Sheet");
if (sheetClone instanceof XSSFSheet) {
XSSFSheet xssfSheet = (XSSFSheet)sheetClone;
// get rid of the wrong relations
for (POIXMLDocumentPart.RelationPart relationPart : xssfSheet.getRelationParts()) {
if (relationPart.getDocumentPart() instanceof org.apache.poi.xssf.usermodel.XSSFVMLDrawing
|| relationPart.getDocumentPart() instanceof org.apache.poi.xssf.model.CommentsTable) {
relationPart.getRelationship().getSource().removeRelationship(relationPart.getRelationship().getId());
}
}
// get rid of the wrong org.apache.poi.xssf.model.CommentsTable
Field sheetComments = XSSFSheet.class.getDeclaredField("sheetComments");
sheetComments.setAccessible(true);
sheetComments.set(xssfSheet, null);
}
Drawing drawing = sheetClone.createDrawingPatriarch();
Comment comment = drawing.createCellComment(drawing.createAnchor(0, 0, 0, 0, 2, 1, 4, 4));
comment.setString(new XSSFRichTextString("Comment in Cell C2 in cloned sheet"));
workbook.write(new FileOutputStream("CopyOfExcelHavingComments.xlsx"));
workbook.close();
}
}
This does not copying the comments from source sheet. But of course this also wil be possible now using Sheet.getCellComments in source sheet and then Drawing.createCellComment in cloned sheet.

CSV generation possible with Apache POI?

I need to generate csv files and I stumbled on a module in our project itself which uses Apache POI to generate excel sheets aleady. So I thought I could use the same to generate csv. So I asked google brother, but he couldnt find anything for sure that says Apache POI can be used for CSV file generation. I was checking on the following api too and it only talks about xls sheets and not csv anywhere. Any ideas?
http://poi.apache.org/apidocs/org/apache/poi/ss/usermodel/Workbook.html
Apache Poi will not output to CSV for you. However, you have a couple good options, depending on what kind of data you are writing into the csv.
If you know that none of your cells will contain csv markers such as commas, quotes, line endings, then you can loop through your data rows and copy the text into a StringBuffer and send that to regular java IO.
Here is an example of writing an sql query to csv along those lines: Poi Mailing List: writing CSV
Otherwise, rather than figure out how to escape the special characters yourself, you should check out the opencsv project
If you check official web site Apache POI, you can find lots of example there. There is also an example that shows how you can have csv formatted output by using apache POI.
ToCSV example
Basic strategy:
1) Apache Commons CSV is the standard library for writing CSV values.
2) But we need to loop through the Workbook ourselves, and then call Commons CSV's Printer on each cell value, with a newline at the end of each row. Unfortunately this is custom code, it's not automatically available in XSSF. But it's easy:
// In this example we construct CSVPrinter on a File, can also do an OutputStream
Reader reader = Files.newBufferedReader(Paths.get(SAMPLE_CSV_FILE_PATH));
CSVPrinter csvPrinter = new CSVPrinter(reader, CSVFormat.DEFAULT);
if (workbook != null) {
XSSFSheet sheet = workbook.getSheetAt(0); // Sheet #0
Iterator<Row> rowIterator = sheet.rowIterator();
while (rowIterator.hasNext()) {
Row row = rowIterator.next();
Iterator<Cell> cellIterator = row.cellIterator();
while (cellIterator.hasNext()) {
Cell cell = cellIterator.next();
csvPrinter.print(cell.getStringCellValue()); // Call Commons CSV here to print
}
// Newline after each row
csvPrinter.println();
}
}
// at the end, close and flush CSVPrinter
csvPrinter.flush();
csvPrinter.close();
An improved and tested version of gene b's response is this:
/**
* Saves all rows from a single Excel sheet in a workbook to a CSV file.
*
* #param excelWorkbook path to the Excel workbook.
* #param sheetNumber sheet number to export.
* #param csvFile CSV file path for output.
* #throws IOException if failed to read the Excel file or create/write to a CSV file.
*/
public static void excelToCsv(String excelWorkbook, int sheetNumber, String csvFile) throws IOException {
try (Workbook workbook = WorkbookFactory.create(new File(excelWorkbook), null, true); // Read-only: true
BufferedWriter writer = new BufferedWriter(new FileWriter(csvFile));
CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT)) {
Sheet sheet = workbook.getSheetAt(sheetNumber);
DataFormatter format = new DataFormatter();
for (Row row : sheet) {
for (int c = 0; c < row.getLastCellNum(); c++) {
// Null cells returned as blank
Cell cell = row.getCell(c, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
String cellValue = format.formatCellValue(cell);
csvPrinter.print(cellValue);
}
csvPrinter.println();
}
csvPrinter.flush();
}
}
The following improvements were made:
NullPointerException won't be thrown if a cell in an Excel Row was never edited. A blank value will be written to the CSV instead.
Excel values are rendered using DataFormatter allowing the CSV to match the visual representation of the Excel sheet.
try-with-source used for auto-close of the file objects.
The workbook is opened in the read-only mode.

apache poi how to disable external reference or external links?

I've been looking on the web for 30 minutes now and can't find any explanation about that. Here is my problem :
I wrote an application with poi to parse some data from 200 excel files or so and put some of it into a new file. I do some cell evaluation with FormulaEvaluator to know the content of the cells before choosing to keep them or not.
Now, when i test it on a test file with only values in the cells, the program works perfectly but when i use it on my pile of files I get this error :
"could not resolve external workbook name"
Is there any way to ignore external workbook references or set up the environment so that it wont evaluate formula with external references?
Because the ones I need don't contain references...
Thank you
Can you not just catch the error, and skip over that cell?
You're getting the error because you've asked POI to evaluate a the formula in a cell, and that formula refers to a different file. However, you've not told POI where to find the file that's referenced, so it objects.
If you don't care about cells with external references, just catch the exception and move on to the next cell.
If you do care, you'll need to tell POI where to find your files. You do this with the setupEnvironment(String[],Evaluator[]) method - pass it an array of workbook names, and a matching array of evaluators for those workbooks.
In order for POI to be able to evaluate external references, it needs access to the workbooks in question. As these don't necessarily have the same names on your system as in the workbook, you need to give POI a map of external references to open workbooks, through the setupReferencedWorkbooks(java.util.Map<java.lang.String,FormulaEvaluator> workbooks) method.
I have done please see below code that is working fine at my side
public static void writeWithExternalReference(String cellContent, boolean isRowUpdate, boolean isFormula)
{
try
{
File yourFile = new File("E:\\Book1.xlsx");
yourFile.createNewFile();
FileInputStream myxls = null;
myxls = new FileInputStream(yourFile);
XSSFWorkbook workbook = new XSSFWorkbook(myxls);
FormulaEvaluator mainWorkbookEvaluator = workbook.getCreationHelper().createFormulaEvaluator();
XSSFWorkbook workbook1 = new XSSFWorkbook(new File("E:\\elk\\lookup.xlsx"));
// Track the workbook references
Map<String,FormulaEvaluator> workbooks = new HashMap<String, FormulaEvaluator>();
workbooks.put("Book1.xlsx", mainWorkbookEvaluator);
workbooks.put("elk/lookup.xlsx", workbook1.getCreationHelper().createFormulaEvaluator());
workbook2.getCreationHelper().createFormulaEvaluator());
// Attach them
mainWorkbookEvaluator.setupReferencedWorkbooks(workbooks);
XSSFSheet worksheet = workbook.getSheetAt(0);
XSSFRow row = null;
if (isRowUpdate) {
int lastRow = worksheet.getLastRowNum();
row = worksheet.createRow(++lastRow);
}
else {
row = worksheet.getRow(worksheet.getLastRowNum());
}
if (!isFormula) {
Cell cell = row.createCell(row.getLastCellNum()==-1 ? 0 : row.getLastCellNum());
cell.setCellValue(Double.parseDouble(cellContent));
} else {
XSSFCell cell = row.createCell(row.getLastCellNum()==-1 ? 0 : row.getLastCellNum());
System.out.println(cellContent);
cell.setCellFormula(cellContent);
mainWorkbookEvaluator.evaluateInCell(cell);
cell.setCellFormula(cellContent);
// mainWorkbookEvaluator.evaluateInCell(cell);
//System.out.println(cell.getCellFormula() + " = "+cell.getStringCellValue());
}
workbook1.close();
myxls.close();
FileOutputStream output_file =new FileOutputStream(yourFile,false);
//write changes
workbook.write(output_file);
output_file.close();
} catch (Exception e) {
e.printStackTrace();
}
}

Resources