Apache poi not allowing blank values for pivot creation - java

I am trying to create a pivot table using apache poi for normal values it is working fine but if there is null or blank values xlsx file gets repaired and pivot gets removed on opening it.
Here is my code:
static void addRowLabel(XSSFPivotTable pivotTable, XSSFSheet dataSheet, AreaReference areaReference, int column) {
DataFormatter formatter = new DataFormatter(java.util.Locale.US);
//apache poi creates as much fields for each as rows are in the pivot table data range
pivotTable.addRowLabel(column);
java.util.TreeSet<String> uniqueItems = new java.util.TreeSet<String>();
for (int r = areaReference.getFirstCell().getRow()+1; r < areaReference.getLastCell().getRow()+1; r++) {
if (dataSheet.getRow(r).getCell(column) != null && dataSheet.getRow(r).getCell(column).getCellType() != CellType.BLANK) {
uniqueItems.add(formatter.formatCellValue(dataSheet.getRow(r).getCell(column)));
} else {
uniqueItems.add("");
}
}
CTPivotField ctPivotField = pivotTable.getCTPivotTableDefinition().getPivotFields().getPivotFieldArray(column);
int i = 0;
for (String item : uniqueItems) {
//take the items as numbered items
ctPivotField.getItems().getItemArray(i).unsetT();
ctPivotField.getItems().getItemArray(i).setX((long)i);
//build a cache definition which has shared elements for those items
pivotTable.getPivotCacheDefinition().getCTPivotCacheDefinition().getCacheFields().getCacheFieldArray(column).getSharedItems().addNewS().setV(item);
i++;
}
//set pivot field settings
ctPivotField.setOutline(false); // no outline format
ctPivotField.setDefaultSubtotal(false); // no subtotals for this field
if (ctPivotField.getDefaultSubtotal()) i++;
for (int k = ctPivotField.getItems().getItemList().size()-1; k >= i; k--) {
ctPivotField.getItems().removeItem(k);
}
ctPivotField.getItems().setCount(i);
}
This method is used for adding rows and below code is to start execution:
public static void secondway( ) throws IOException {
try (XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream("/opt/source.xlsx"));
FileOutputStream fileout = new FileOutputStream("/opt/ExcelResult.xlsx") ) {
XSSFSheet dataSheet = workbook.getSheetAt(0);
XSSFSheet pivotSheet = workbook.createSheet("Pivot");
int firstRow = dataSheet.getFirstRowNum();
int lastRow = dataSheet.getLastRowNum();
int firstCol = dataSheet.getRow(0).getFirstCellNum();
int lastCol = dataSheet.getRow(0).getLastCellNum();
CellReference topLeft = new CellReference(firstRow, firstCol);
CellReference botRight = new CellReference(lastRow, lastCol - 1);
AreaReference areaReference = new AreaReference(topLeft,botRight, SpreadsheetVersion.EXCEL2007);
XSSFPivotTable pivotTable = pivotSheet.createPivotTable(areaReference, new CellReference("A1"), dataSheet);
addRowLabel(pivotTable, dataSheet, areaReference, 0);
addRowLabel(pivotTable, dataSheet, areaReference, 2);
pivotTable.addColumnLabel(DataConsolidateFunction.SUM, 1, "test");
workbook.write(fileout);
}
}
I am not sure what is wrong am I doing or how to support blank values. Please help.
Input that I am using:
While opening shows below error and removes piivot:
[![enter image description here][3]][3]

The aim of the addRowLabel method, which seems to be from my answer java: How to create a pivot with apache poi?, is to correct apache poi, which creates as much items for each pivot field as rows are in the pivot table data range. But it should be as much items as unique items are in pivot field data column.
To get the unique items per column a java.util.TreeSet is used as this cannot contain duplicate elements.
But Excel pivot table takes the values case insensitive. So 11 KD and 11 kd are the same value for Excel pivot tables. Thats why String.CASE_INSENSITIVE_ORDER needs to be used as Comparator while creating the java.util.TreeSet.
Do changing:
...
java.util.TreeSet<String> uniqueItems = new java.util.TreeSet<String>();
...
into
...
java.util.TreeSet<String> uniqueItems = new java.util.TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
...
and it should work.

Related

POI Word Unable to merge newly created cell vertically

I know how to merge cells vertically with Apache POI word. But it seems if a new row is created, the merge won't take effect.
Here is the input table:
I wish to add a new row between old row 2 and old row 3, and have the new row's cell at first column merged into C2, like this:
So I created a new row and added it to the table below old row 2, and attempt to merge the cells
github source code link is here, it can reproduce the problem.
public class POIWordAddSubRowQuestionDemo{
public static void main(String[] args) throws IOException, XmlException{
ClassLoader classLoader = POIWordAddSubRowQuestionDemo.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("input.docx");
String outputDocxPath = "F:/TEMP/output.docx";
assert inputStream != null;
XWPFDocument doc = new XWPFDocument(inputStream);
XWPFTable table = doc.getTables().get(0);
//this is 'old row 2'
XWPFTableRow secondRow = table.getRows().get(1);
//create a new row that is based on 'old row 2'
CTRow ctrow = CTRow.Factory.parse(secondRow.getCtRow().newInputStream());
XWPFTableRow newRow = new XWPFTableRow(ctrow, table);
XWPFRun xwpfRun = newRow.getCell(1).getParagraphs().get(0).getRuns().get(0);
//set row text
xwpfRun.setText("new row", 0);
// add new row below 'old row 2'
table.addRow(newRow, 2);
//merge cells at first column of 'old row 2', 'new row', and 'old row 3'
mergeCellVertically(doc.getTables().get(0), 0, 1, 3);
FileOutputStream fos = new FileOutputStream(outputDocxPath);
doc.write(fos);
fos.close();
}
static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) {
for(int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
CTVMerge vmerge = CTVMerge.Factory.newInstance();
if(rowIndex == fromRow){
// The first merged cell is set with RESTART merge value
vmerge.setVal(STMerge.RESTART);
} else {
// Cells which join (merge) the first one, are set with CONTINUE
vmerge.setVal(STMerge.CONTINUE);
// and the content should be removed
for (int i = cell.getParagraphs().size(); i > 0; i--) {
cell.removeParagraph(0);
}
cell.addParagraph();
}
// Try getting the TcPr. Not simply setting an new one every time.
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr == null) tcPr = cell.getCTTc().addNewTcPr();
tcPr.setVMerge(vmerge);
}
}
}
But the merge did not work and I got:
In another attempt, I tried to merge based on the table in picture 3 to get the table in picture 2, and it was a success. The only difference between the 2 attempts is that new row was not newly created, but rather read from the docx document, so I believe creating a new row was the reason why merge failed.
So is there a solution for merging newly created rows? I really don't want to split this operation like this: adding rows > saving docx to disk> read docx from disk> merge rows.
The problem you have is not with mergeCellVertically method but with your approach to copy table row. When copying the underlying CTRow and inserting it in CTTbl.TrArray using XWPFTable.addRow it must be fully complete. Later changings are not written in XML. I told that in my answer java Apache POI Word existing table insert row with cell style and formatting already. And I provided a method commitTableRows in my answer Can't change row text in .docx file once row is added to table. This method needs to be called before writing out the document, so the later changes get written in XML.
So because you are copying second row, which is the start of merging, that setting also gets copied. And the later called mergeCellVertically does not take effect. So your newRow remains new start of merging. This is what you get.
So after all changes and before writing out, call commitTableRows.
Complete example:
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
public class WordInsertTableRowAndMerge {
static XWPFTableRow insertNewTableRow(XWPFTableRow sourceTableRow, int pos) throws Exception {
XWPFTable table = sourceTableRow.getTable();
CTRow newCTRrow = CTRow.Factory.parse(sourceTableRow.getCtRow().newInputStream());
XWPFTableRow tableRow = new XWPFTableRow(newCTRrow, table);
table.addRow(tableRow, pos);
return tableRow;
}
static void commitTableRows(XWPFTable table) {
int rowNr = 0;
for (XWPFTableRow tableRow : table.getRows()) {
table.getCTTbl().setTrArray(rowNr++, tableRow.getCtRow());
}
}
static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) {
for(int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
System.out.println("rowIndex: " + rowIndex);
XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
CTVMerge vmerge = CTVMerge.Factory.newInstance();
if(rowIndex == fromRow){
// The first merged cell is set with RESTART merge value
vmerge.setVal(STMerge.RESTART);
} else {
// Cells which join (merge) the first one, are set with CONTINUE
vmerge.setVal(STMerge.CONTINUE);
// and the content should be removed
for (int i = cell.getParagraphs().size(); i > 0; i--) {
cell.removeParagraph(0);
}
cell.addParagraph();
}
// Try getting the TcPr. Not simply setting an new one every time.
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr == null) tcPr = cell.getCTTc().addNewTcPr();
tcPr.setVMerge(vmerge);
}
}
public static void main(String[] args) throws Exception {
XWPFDocument doc = new XWPFDocument(new FileInputStream("./source.docx"));
XWPFTable table = doc.getTables().get(0);
XWPFTableRow row = table.getRow(1);
XWPFTableRow newRow = insertNewTableRow(row, 2);
XWPFTableCell cell = newRow.getCell(0); if (cell == null) cell = newRow.addNewTableCell();
// not needed because merged to cell above
cell = newRow.getCell(1); if (cell == null) cell = newRow.addNewTableCell();
for (XWPFParagraph paragraph : cell.getParagraphs()) { // only use first text runs in paragraphs
for (int r = paragraph.getRuns().size()-1; r >= 0; r--) {
XWPFRun run = paragraph.getRuns().get(r);
if (r == 0) {
run.setText("new row 1", 0);
} else {
paragraph.removeRun(r);
}
}
}
mergeCellVertically(table, 0, 1, 3);
commitTableRows(table);
FileOutputStream out = new FileOutputStream("./result.docx");
doc.write(out);
out.close();
doc.close();
}
}
Here's a VBA approach you might like to adapt. It inserts a new row between rows 3 & 4:
Sub Demo()
Application.ScreenUpdating = False
With ActiveDocument.Tables(1)
.Cell(4, 2).Range.InsertBreak (wdColumnBreak)
.Rows.Add
.Cell(2, 1).Merge MergeTo:=.Cell(4, 1)
.Range.Characters.Last.Next.Delete
.Cell(2, 1).Merge MergeTo:=.Cell(5, 1)
End With
Application.ScreenUpdating = True
End Sub

Reading from excel files using JAVA poi and ignoring empty cells

I was trying to read data from an excel sheet that contains empty cells consider the following code:
File src = new File(path to your file);
FileInputStream fis = new FileInputStream(src);
XSSFWorkbook wb = new XSSFWorkbook(fis);
XSSFSheet sheet1 = wb.getSheet("Sheet1");
int rowCount = sheet1.getLastRowNum();
System.out.println("total number of rows is: " + rowCount);
for(int i = 1; i < rowCount; i++) {
//getcell returns row num and starts from 1
//Also my sheet column contains data in numeric form
int data = (int) sheet1.getRow(i).getCell(1).getNumericCellValue();
System.out.println(data);
}
However, my code also reads the empty cells and displays the value as 0 (The cells have numeric value). How do I make my script read-only cells that are filled and display them while ignoring the ones that are empty?
Thank you in advance
Just update the for loop with an if condition which will check for the value which is getting retrieved from the Cell. PFB the updated for loop.
For Apache POI version 4.x you can try below-
for(int i = 1; i < rowCount; i++) {
//getcell returns row num and starts from 1
//Also my sheet column contains data in numeric form
Cell cell = sheet1.getRow(i).getCell(1);
int data = (int) sheet1.getRow(i).getCell(1).getNumericCellValue();
if(c.getCellType() == CellType.Blank)
{
continue;
}
else{
System.out.println(data);
}
}
For Apache POI version 3.x you can try below-
for(int i = 1; i < rowCount; i++) {
//getcell returns row num and starts from 1
//Also my sheet column contains data in numeric form
Cell cell = sheet1.getRow(i).getCell(1);
int data = (int) sheet1.getRow(i).getCell(1).getNumericCellValue();
if(cell.getCellType() == Cell.CELL_TYPE_BLANK)
{
continue;
}
else{
System.out.println(data);
}
}

Apache Poi - createCell() Method doesn't accept index via for loop as parameter

I want to create an Excel file with Apache Poi, based on the sheet of another Excel file. Only the first two columns and their corresponding rows should be applied to the new Excel sheet.
First, I insert all cells of the first column, then I increment the columnIndex to insert the other cells.
private static void createNewWorkBook(XSSFSheet oldSheet) {
XSSFWorkbook newWorkbook = new XSSFWorkbook();
XSSFSheet newSheet = newWorkbook.createSheet("test-sheet");
for (int columnIndex = 0; columnIndex < 2; columnIndex++) {
int rowIndex = 0;
for (Row oldRow : oldSheet) {
XSSFRow newRow = newSheet.createRow(rowIndex);
XSSFCell newCell = newRow.createCell(columnIndex);
newCell.setCellValue("Hello"); // just for test purposes
// newCell.setCellValue(oldSheet.getRow(rowIndex).getCell(columnIndex).getStringCellValue());
rowIndex++;
}
}
try {
FileOutputStream fos = new FileOutputStream(new File("CreateExcelDemo.xlsx"));
newWorkbook.write(fos);
fos.close();
} catch (
IOException e) {
e.printStackTrace();
}
}
Unfortunatley it doesn't work. I only get the values of the second column in my newly generated excel-sheet. The first column is just empty.
BUT:
If I replace the columnIndex with 0 or 1, it works! Where is my thinking problem?

How to have excel formula refresh when new data inserted with Java through Apache POI

I am inserting a list of numbers into a column in Excel (column A) with Java. the following column has a list of average numbers (Column B). Column C has a list of formulas like this: =IF(B1 < 1,0,((A1)-B1)/B1).
In my program, I want to have the formulas in column C re-evaluate after I've input the new data so then I can extract the new cell value into Java. I'm checking to see if any new result in column C is -1.0.
I've tried to use FormulaEvaluator before extracting column C values but I still get the old values. I've also tried writing and closing the workbook and opening it again but that also hasn't worked.
//Pull the values stored procedure
ResultSet rs = dbutils.sqlserverdbvaliation(query, db);
System.out.println("Returned the Result Set");
//Get number of rows in result row to set array size
rs.last();
arraySize = rs.getRow();
rs.beforeFirst();
//Store values from resultSet into an array
int[] dlArray = new int[arraySize];
while(rs.next()) {
//Activity Process Count is name of ResultSet Column
dlArray[count] = rs.getInt("ActivityProcessCount");
count++;
}
//Create workbook
InputStream inp = new FileInputStream("C:\\TestWorkbook.xlsx");
Workbook wb = WorkbookFactory.create(inp);
org.apache.poi.ss.usermodel.Sheet sheet = wb.getSheetAt(1);
FormulaEvaluator evaluator =
wb.getCreationHelper().createFormulaEvaluator();
evaluator.evaluateAll();
System.out.println(Arrays.toString(dlArray));
//Write the values from the array to the spreadsheet
int excelCount = 1;
for (int i = 0; i < arraySize ; i++) {
Row row = sheet.getRow(excelCount);
Cell cell = row.getCell(2);
cell.setCellValue(dlArray[i]);
evaluator.evaluateAll();
excelCount++;
}
FileOutputStream fos = new FileOutputStream("C:\\TestWorkbook.xlsx");
evaluator.evaluateAll();
wb.write(fos);
wb.close();
InputStream inp2 = new FileInputStream("C:\\TestWorkbook.xlsx");
Workbook wb2 = WorkbookFactory.create(inp2);
org.apache.poi.ss.usermodel.Sheet sheet2 = wb2.getSheetAt(1);
excelCount = 1;
for (int i = 0; i < arraySize ; i++) {
Row row = sheet2.getRow(excelCount);
Cell cell = row.getCell(4);
if (cell.getNumericCellValue() == -1.0) {
ExcelUtils.setCellStyle(excelPath, sheetName, excelCount, 5,
YELLOW);
}
else {
ExcelUtils.setCellStyle(excelPath, sheetName, excelCount, 5,
GREEN);
}
excelCount++;
}

Inserting data from arraylist in chunks in excel file using apache poi

I have the arraylist of data in the following format :
ArrayList> listResultData. Now collection contains around 11k+ rows to be inserted in the excel.
When i insert these 11490 rows in excel it took 6 hrs to insert the records, that means its very bad performance issue. Is there anyway to insert the data in excel in chunks for 1000 rows at a time (means there should be something like executeBatch() in sql for inserting records). A row contains 4-5 columns also.
Following is the code i have been using :
public boolean setArrayListData(String sheetName, ArrayList<ArrayList<String>> listResultData) {
try {
fis = new FileInputStream(path);
workbook = new XSSFWorkbook(fis);
int index = workbook.getSheetIndex(sheetName);
if (index == -1)
return false;
sheet = workbook.getSheetAt(index);
int colNum = 0;
int rowNum = this.getRowCount(sheetName);
rowNum++;
for (ArrayList<String> al : listResultData) {
for (String s : al) {
sheet.autoSizeColumn(colNum);
row = sheet.getRow(rowNum - 1);
if (row == null)
row = sheet.createRow(rowNum - 1);
cell = row.getCell(colNum);
if (cell == null)
cell = row.createCell(colNum);
// cell style
// CellStyle cs = workbook.createCellStyle();
// cs.setWrapText(true);
// cell.setCellStyle(cs);
cell.setCellValue(s);
//System.out.print("Cell Value :: "+s);
colNum++;
}
rowNum++;
colNum = 0;
//System.out.println("");
}
fileOut = new FileOutputStream(path);
workbook.write(fileOut);
fileOut.close();
workbook.close();
fis.close();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
Please suggest !!
Instead of XSSF you may want to try SXSSF the streaming extension of XSSF. In contrast to xssf where you have access to all rows in the document which can lead to performance or heap space issue sxssf allows you to define a sliding window and limits the access to rows in that window. You can specify the window size at construction time of your workbook using new SXSSFWorkbook(int windowSize) . As you then create your rows and the number of rows exceed the specified window size, the row with the lowest index is flushed and is no longer in memory.
Find further infos at SXSSF (Streaming Usermodel API)
Example:
// keep 100 rows in memory, exceeding rows will be flushed to disk
SXSSFWorkbook wb = new SXSSFWorkbook(100);
Sheet sh = wb.createSheet();
for(int rownum = 0; rownum < 1000; rownum++){
//When the row count reaches 101, the row with rownum=0 is flushed to disk and removed from memory,
//when rownum reaches 102 then the row with rownum=1 is flushed, etc.
Row row = sh.createRow(rownum);
for(int cellnum = 0; cellnum < 10; cellnum++){
Cell cell = row.createCell(cellnum);
String address = new CellReference(cell).formatAsString();
cell.setCellValue(address);
}
}

Categories

Resources