APACHE POI set formula by column name format - java

Have small problem when I want to set formula to cell by column name format. Let me show example:
In excel file I can do something like this =[Name]
So value from colmn B is copied to column A
But when I try to do something like this in Apache POI
cell.setCellFormula("[Name]");
I get exception:
Parse error near char 0 '[' in specified formula '[Name]'. Expected number, string, defined name, or data table"
How can I handle such situation?

The formula =[Name] is a structured reference to an Excel table. But it is a very short unqualified form. The fully qualified form would be =tableName[[#This Row],[Name]], And that fully qualified form also will be stored and so must be set using apache poi.
Let's have a complete example:
import java.io.FileOutputStream;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
class CreateExcelTableUsingStructuredReferences {
public static void main(String[] args) throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook();
FileOutputStream fileout = new FileOutputStream("./Excel.xlsx") ) {
//data
String[] tableHeadings = new String[]{"Id", "Name", "Description"};
String[] tableContent = new String[]{null, "Name1", "Description1"};
//variables
String tableName = "Table1";
int firstRow = 0; //start table in row 1
int firstCol = 0; //start table in column A
int rows = 2; //we have to populate headings row and 1 data row
int cols = 3; //three columns in each row
//prepairing the sheet
XSSFSheet sheet = workbook.createSheet();
//set sheet content
for (int r = 0; r < rows; r++) {
XSSFRow row = sheet.createRow(firstRow+r);
for (int c = 0; c < cols; c++) {
XSSFCell localXSSFCell = row.createCell(firstCol+c);
if (r == 0) {
localXSSFCell.setCellValue(tableHeadings[c]);
} else {
localXSSFCell.setCellValue(tableContent[c]);
}
}
}
//create the table
CellReference topLeft = new CellReference(sheet.getRow(firstRow).getCell(firstCol));
CellReference bottomRight = new CellReference(sheet.getRow(firstRow+rows-1).getCell(firstCol+cols-1));
AreaReference tableArea = workbook.getCreationHelper().createAreaReference(topLeft, bottomRight);
XSSFTable dataTable = sheet.createTable(tableArea);
dataTable.setName(tableName);
dataTable.setDisplayName(tableName);
//set table column formula
dataTable.getCTTable().getTableColumns().getTableColumnList().get(0).addNewCalculatedColumnFormula().setStringValue(
tableName + "[[#This Row],[Name]]");
//set the formula in sheet
XSSFCell formulaCell = sheet.getRow(firstRow+1).getCell(firstCol);
formulaCell.setCellFormula(tableName + "[[#This Row],[Name]]");
//following is not necessary up to apache poi 5.1.0, but later versions of apache poi uses formula parser which damages structured table formulas
formulaCell.getCTCell().getF().setStringValue(tableName + "[[#This Row],[Name]]");
workbook.write(fileout);
}
}
}
This works using apache poi 4 or later.
Note, the additional formulaCell.getCTCell().getF().setStringValue(tableName + "[[#This Row],[Name]]"); is only needed using apache poi versions after 5.1.0. Later versions of apache poi uses a formula parser which damages the structured table formula and so it must be reset using this code line.

Related

How to read merged cell from excel data and keep that in mail body as html table structure using java?

`Execel sheets contains data which was structured as normal cells and merge cells. The assignment is to keep the exact structure in email body content using java(with same merge/normal cells) and using mimemessage to send, apache POI 3.8 version for excel coding.
My requirement is to display the data in email body which has exact excel structure.
tried below code to achieve but when the cell is merged cell am not able to display in mail using html structure in java.
sample excel structure and the below one should represent in mail body as well:
program:
import org.apache.poi.ss.util.CellRangeAddress;
StringBuilder body = new StringBuilder();
body.append("<table>");
for (int rowNum = sheet.getFirstRowNum(); rowNum <= sheet.getLastRowNum(); rowNum++) {
body.append("<tr>");
for (int colNum = 0; colNum < sheet.getRow(rowNum).getLastCellNum(); colNum++) {
boolean isMerged = false;
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
CellRangeAddress cellRangeAddress = sheet.getMergedRegion(i);
int firstRow = cellRangeAddress.getFirstRow();
int lastRow = cellRangeAddress.getLastRow();
int firstColumn = cellRangeAddress.getFirstColumn();
int lastColumn = cellRangeAddress.getLastColumn();
body.append("<td>");
body.append(sheet.getRow(rowNum).getCell(colNum).getStringCellValue());
body.append("</td>");
}
body.append("</tr>");
}
body.append("</table>");
below is the output i am getting which is not as per expection:
Please let me know how to achieve the expected format.

when different header are set for the generated excel sheet, pivot table is not generating

I have created a excel sheet which generates the PIVOT table using apache POI. Issue is when the column header is set by giving different header names, the PIVOT table is not generating and showing and alert error message as below.
Removed Feature: PivotTable report from /xl/pivotCache/pivotCacheDefinition1.xml part (PivotTable cache)
Is there a way i can set different names for the column headers for the data sheet from which pivot table is generated.
Below is the sample code for setting different column names for the excel sheet.
XSSFSheet dataSheet = workbook.createSheet("SID Details");
//some business logic
int rowCount = 0;
String[] headers = new String[]{
"SID", "SID Account", "Approved Percent", "Comments"};
XSSFRow row = sheet.createRow(rowCount++);
for (String fieldName : headers) {
row.createCell(columnCount++).setCellValue(fieldName);
}
//logic
XSSFSheet sheet = workbook.createSheet("PIVOT SHEET");
Map<String, Map<String, Long>> map = myList.stream().collect(
groupingBy(SLoan::getStatus,
groupingBy(SLoan::getUser, counting())));
int firstPivotTableRowCount = map.values().stream().mapToInt(stringLongMap -> stringLongMap.size() + 1).sum();
//dynamic cell
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 ar = new AreaReference(topLeft, botRight, SpreadsheetVersion.EXCEL2007);
/* Where the Pivot Table needs to be placed */
CellReference cr1 = new CellReference("A1");

XSSFTable setDataRowCount overwrites totals row [duplicate]

I've got an table in excel with formulae I would like to add data to.
My motivation for this is the fact that tables in excel can dynamically expand to the range of data you add to them, meaning that the formula rows automatically keep up with the amount of data rows.
I'm however having a hard time finding out if this is possible using apache-POI.
One thing I was going to try (see code below) was to expand the AreaReference of the table to cover the data, however both AreaReference(CR,CR2); (as used in this example) and AreaReference(CR,CR2, SpreadsheetVersion.EXCEL2007) (seen in the apache docs) give "constructor is undefined".
No idea what is causing that constructor error as I do have org.apache.poi.ss.util imported.
The other option on the apache docs AreaReference(java.lang.String reference) lets me compile and run but instead gives a "NoSuchMethod" error.
List<XSSFTable> tableList = spreadSheet.getTables();
CellReference CR = new CellReference(0, 0);
CellReference CR2 = new CellReference(5, 2);
AreaReference my_data_range = new AreaReference(CR,CR2);
tableList.get(0).setArea(my_data_range);
Any help will be appreciated.
The main problem using apache poi until now is that it is not ready to be used without having detailed knowledge about Microsoft Office as such and about the storage of Microsoft Office files. There are many things only half way ready and there are regressions often in new versions (bugs occur again which were solved already).
So your requirement: "Expanding an existing table in Excel using Apache POI" is not possible only simply using apache poi. One must know that Office Open XML files *.xlsx are simply ZIP archives which can be unzipped. And after unzipping we find /xl/tables/table1.xml for storage of the table. This XML we can analyzing and comparing it with XML which was created using Excel's GUI. So we can find problems which results from shortcomings of apache poi. Same is with the sheet's XML in /xl/tables/sheet1.xml.
Also we need to know that apache poi builds on the low level classes of ooxml-schemas. Partially we need using those classes because of the halfway readiness of apache poi. In the following example we need ooxml-schemas-1.4.jar additionally because apache poi's poi-ooxml-schemas-4.0.0.jar has not included org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableFormula until now. Unfortunately there is no documentation about ooxml-schemas public available. So we need downloading the sources and doing javadoc our own.
The following example works for me using apache poi 4.0.0. If you get problems while compiling or running, the reason might be that multiple different versions of apache poi jars are in class path while compile time and/or run time. Do not mix different apache poi versions. Also, as said already, my code needs the full jar of all of the schemas ooxml-schemas-1.4.jar.
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.ss.SpreadsheetVersion;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
class ExcelExpandingTable {
static void addRowToTable(XSSFTable table) {
int lastTableRow = table.getEndCellReference().getRow();
int totalsRowCount = table.getTotalsRowCount();
int lastTableDataRow = lastTableRow - totalsRowCount;
// we will add one row in table data
lastTableRow++;
lastTableDataRow++;
// new table area plus one row
AreaReference newTableArea = new AreaReference(
table.getStartCellReference(),
new CellReference(
lastTableRow,
table.getEndCellReference().getCol()
),
SpreadsheetVersion.EXCEL2007
);
// new table data area plus one row
AreaReference newTableDataArea = new AreaReference(
table.getStartCellReference(),
new CellReference(
lastTableDataRow,
table.getEndCellReference().getCol()
),
SpreadsheetVersion.EXCEL2007
);
XSSFSheet sheet = table.getXSSFSheet();
if (totalsRowCount > 0) {
//if we have totals rows, shift totals rows down
sheet.shiftRows(lastTableDataRow, lastTableRow, 1);
// correcting bug that shiftRows does not adjusting references of the cells
// if row 3 is shifted down, then reference in the cells remain r="A3", r="B3", ...
// they must be adjusted to the new row thoug: r="A4", r="B4", ...
// apache poi 3.17 has done this properly but had have other bugs in shiftRows.
for (int r = lastTableDataRow; r < lastTableRow + 1; r++) {
XSSFRow row = sheet.getRow(r);
if (row != null) {
long rRef = row.getCTRow().getR();
for (Cell cell : row) {
String cRef = ((XSSFCell)cell).getCTCell().getR();
((XSSFCell)cell).getCTCell().setR(cRef.replaceAll("[0-9]", "") + rRef);
}
}
}
// end correcting bug
}
// if there are CalculatedColumnFormulas do filling them to the new row
XSSFRow row = sheet.getRow(lastTableDataRow); if (row == null) row = sheet.createRow(lastTableDataRow);
int firstTableCol = table.getStartCellReference().getCol();
for (CTTableColumn tableCol : table.getCTTable().getTableColumns().getTableColumnList()) {
if (tableCol.getCalculatedColumnFormula() != null) {
int id = (int)tableCol.getId();
String formula = tableCol.getCalculatedColumnFormula().getStringValue();
XSSFCell cell = row.getCell(firstTableCol + id - 1); if (cell == null) cell = row.createCell(firstTableCol + id - 1);
cell.setCellFormula(formula);
}
}
table.setArea(newTableArea);
// correcting bug that Autofilter includes possible TotalsRows after setArea new
// Autofilter must only contain data area
table.getCTTable().getAutoFilter().setRef(newTableDataArea.formatAsString());
// end correcting bug
table.updateReferences();
}
public static void main(String[] args) throws Exception {
try (Workbook workbook = WorkbookFactory.create(new FileInputStream("SAMPLE.xlsx"));
FileOutputStream out = new FileOutputStream("SAMPLE_NEW.xlsx")) {
XSSFSheet sheet = ((XSSFWorkbook)workbook).getSheetAt(0);
XSSFTable table = sheet.getTables().get(0);
addRowToTable(table);
workbook.write(out);
}
}
}
The above was 2018. Now we have 2022.
Using current apache poi 5.2.2 the code may be like follows:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.ss.SpreadsheetVersion;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
class ExcelExpandingTable {
static XSSFRow addRowToTable(XSSFTable table) {
int lastTableRow = table.getEndCellReference().getRow();
int totalsRowCount = table.getTotalsRowCount();
int lastTableDataRow = lastTableRow - totalsRowCount;
int firstTableCol = table.getStartCellReference().getCol();
// we will add one row in table data
lastTableRow++;
lastTableDataRow++;
// new table area plus one row
AreaReference newTableArea = new AreaReference(
table.getStartCellReference(),
new CellReference(
lastTableRow,
table.getEndCellReference().getCol()
),
SpreadsheetVersion.EXCEL2007
);
XSSFSheet sheet = table.getXSSFSheet();
if (totalsRowCount > 0) {
//if we have totals rows, shift totals rows down
sheet.shiftRows(lastTableDataRow, lastTableRow, 1);
//correct all sheet table-reference-formulas which probably got damaged after shift rows
for (CTTableColumn tableCol : table.getCTTable().getTableColumns().getTableColumnList()) {
if (tableCol.getCalculatedColumnFormula() != null) {
int id = (int)tableCol.getId();
String formula = tableCol.getCalculatedColumnFormula().getStringValue();
int rFirst = table.getStartCellReference().getRow() + table.getHeaderRowCount();
int rLast = table.getEndCellReference().getRow() - table.getTotalsRowCount();
int c = table.getStartCellReference().getCol() + id - 1;
sheet.getWorkbook().setCellFormulaValidation(false);
for (int r = rFirst; r <= rLast; r++) {
XSSFRow row = sheet.getRow(r); if (row == null) row = sheet.createRow(r);
XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
cell.setCellFormula(formula);
}
}
}
}
// if there are CalculatedColumnFormulas do filling them to the new row
XSSFRow row = sheet.getRow(lastTableDataRow); if (row == null) row = sheet.createRow(lastTableDataRow);
for (CTTableColumn tableCol : table.getCTTable().getTableColumns().getTableColumnList()) {
if (tableCol.getCalculatedColumnFormula() != null) {
int id = (int)tableCol.getId();
String formula = tableCol.getCalculatedColumnFormula().getStringValue();
XSSFCell cell = row.getCell(firstTableCol + id - 1); if (cell == null) cell = row.createCell(firstTableCol + id - 1);
cell.getSheet().getWorkbook().setCellFormulaValidation(false); // see https://bz.apache.org/bugzilla/show_bug.cgi?id=66039
cell.setCellFormula(formula);
}
}
// copy cell styles to the new row from the row above
row = sheet.getRow(lastTableDataRow); if (row == null) row = sheet.createRow(lastTableDataRow);
XSSFRow rowAbove = sheet.getRow(lastTableDataRow - 1); if (row == null) row = sheet.createRow(lastTableDataRow - 1);
for (CTTableColumn tableCol : table.getCTTable().getTableColumns().getTableColumnList()) {
int id = (int)tableCol.getId();
XSSFCell cellAbove = rowAbove.getCell(firstTableCol + id - 1);
if (cellAbove != null) {
XSSFCellStyle styleAbove = cellAbove.getCellStyle();
XSSFCell cell = row.getCell(firstTableCol + id - 1); if (cell == null) cell = row.createCell(firstTableCol + id - 1);
cell.setCellStyle(styleAbove);
}
}
// set new table area
table.setArea(newTableArea);
// update table references
table.updateReferences();
return sheet.getRow(lastTableDataRow);
}
public static void main(String[] args) throws Exception {
try (Workbook workbook = WorkbookFactory.create(new FileInputStream("SAMPLE.xlsx"));
FileOutputStream out = new FileOutputStream("SAMPLE_NEW.xlsx")) {
XSSFSheet sheet = ((XSSFWorkbook)workbook).getSheetAt(0);
XSSFTable table = sheet.getTables().get(0);
XSSFRow row = addRowToTable(table);
workbook.write(out);
}
}
}
According to FAQ, this code needs poi-ooxml-full-5.2.2.jar instead of ooxml-schemas-1.4.jar now for run using apache poi 5.2.2.
Some bugs are fixed. But there is one new bug when using XSSFCell.setCellFormula when formula contains table references. But XSSFWorkbook.setCellFormulaValidation(false) avoids this.

How to set default value in Apache POI pivot table report filter

I have a worksheet with data in it, I am trying to create a pivot table with report filter. I want to set default value to the report filter.
pivotTable.addReportFilter(13);
column contains 0's and 1's, I would like to set 0 as my default value in the report filter.
At first this question is not answerable in that general context as it is asked now. Creating pivot tables using apache poi is in beta state until now. So we need not only the high level apache poi API but also the underlying low level objects. And we need exactly to know which kind of data shall be in the pivot table. To be general able creating pivot tables from all kind of data, as Excel can do, there is much more effort necessary. Microsoft has programmed this in decades with big teams of programmers. From this apache poi is far away.
Until now apache poi adds as much pivot field items of type "default" (<item t="default"/>) as rows are present in the data range, if the pivot fields where used as axis fields.
This is because they don't want to have a look at the data, and so they are assuming as much different values as rows where in the data.
This is fine because Excel will rebuild its pivot cache while opening. But if we want preselect items, then this is not fine. Then we must know what items there are that can be preselected.
So we need at least as much items, as we want preselecting, as numbered items: <item x="0"/><item x="1"/><item x="2"/>...
And we need to build a cache definition which has shared elements for those items.
Example:
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.*;
import java.util.Random;
import java.io.*;
class PivotTableTest4 {
private static void setCellData(Sheet sheet) {
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("Name");
cell = row.createCell(1);
cell.setCellValue("Value1");
cell = row.createCell(2);
cell.setCellValue("Value2");
cell = row.createCell(3);
cell.setCellValue("City");
for (int r = 1; r < 15; r++) {
row = sheet.createRow(r);
cell = row.createCell(0);
cell.setCellValue("Name " + ((r-1) % 4 + 1));
cell = row.createCell(1);
cell.setCellValue(r * new java.util.Random().nextDouble());
cell = row.createCell(2);
cell.setCellValue(r * new java.util.Random().nextDouble());
cell = row.createCell(3);
cell.setCellValue("City " + ((r-1) % 3 + 1));
}
}
public static void main(String[] args) {
try {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet sheet = wb.createSheet();
//Create some data to build the pivot table on
setCellData(sheet);
XSSFPivotTable pivotTable = sheet.createPivotTable(
new AreaReference(new CellReference("A1"), new CellReference("D15")), new CellReference("H5"));
//Configure the pivot table
//Use first column as row label
pivotTable.addRowLabel(0);
//Sum up the second column
pivotTable.addColumnLabel(DataConsolidateFunction.SUM, 1);
//Avarage the third column
pivotTable.addColumnLabel(DataConsolidateFunction.AVERAGE, 2);
//Add fourth column as page filter
pivotTable.addReportFilter(3);
/*
Apache poi adds 15 pivot field items of type "default" (<item t="default"/>) here.
This is because there are 15 rows (A1:D15) and, because they don't have a look at the data,
they are assuming max 15 different values. This is fine because Excel will rebuild its pivot cache while opening.
But if we want preselect items, then this is not fine. Then we must know what items there are that can be preselected.
So we need at least as much items as we want preselecting as numbered items: <item x="0"/><item x="1"/><item x="2"/>...
And we must build a cache definition which has shared elements for those items.
*/
for (int i = 0; i < 3; i++) {
//take the first 3 items as numbered items: <item x="0"/><item x="1"/><item x="2"/>
pivotTable.getCTPivotTableDefinition().getPivotFields().getPivotFieldArray(3).getItems().getItemArray(i).unsetT();
pivotTable.getCTPivotTableDefinition().getPivotFields().getPivotFieldArray(3).getItems().getItemArray(i).setX((long)i);
//build a cache definition which has shared elements for those items
//<sharedItems><s v="City 1"/><s v="City 2"/><s v="City 3"/></sharedItems>
pivotTable.getPivotCacheDefinition().getCTPivotCacheDefinition().getCacheFields().getCacheFieldArray(3).getSharedItems().addNewS().setV("City " + (i+1));
}
//Now we can predefinite a page filter. Second item, which is "City 2", in this case.
pivotTable.getCTPivotTableDefinition().getPageFields().getPageFieldArray(0).setItem(1);
FileOutputStream fileOut = new FileOutputStream("PivotTableTest4.xlsx");
wb.write(fileOut);
fileOut.close();
wb.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
This needs the full jar of all of the schemas, ooxml-schemas-1.3.jar, as mentioned in the FAQ.

How to get excell range name for HSSFCell apache POI API?

As title, I have a little trouble here.
I can get font from cell
HSSFFont font =
cell.getRow().getSheet().getWorkbook().
getFontAt(cell.getCellStyle().getFontIndex());
But now I need to get range name of it. Actually I need something to anchor and determine key cell and its own value cell.
Is there some method to get range name such as workBook.getName() or .getNameAt() but how to get name index from HSSFCell?
Apart from rich text strings, a cell has only one font assigned,
but it may be referenced by more than one named range.
So you'll need to iterate through the named ranges of the workbook and check if the cell is referenced. For the sake of simplicity, I have iterated over all area.getAllReferencedCells() - in case of big ranges you'll need to check if the area isContiguous() and if your cell/row-index is inside the cell/row-index of getFirstCell() and getLastCell() bounding box.
For more info check the Busy Developers' Guide to HSSF and XSSF Features.
Or search on stackoverflow ...
(in my testcase, a cell(row 4, col 3) was referenced by three different shaped named ranges)
import java.io.File;
import java.util.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.*;
public class XlsRangeNames {
public static void main(String[] args) throws Exception {
Workbook wb = WorkbookFactory.create(new File("src/test/resources/name-range.xls"));
Cell cell = wb.getSheetAt(0).getRow(3).getCell(2);
for (Name n : getNamedRangesForCell(wb, cell)) {
System.out.println(n.getNameName());
}
}
static List<Name> getNamedRangesForCell(Workbook wb, Cell cell) {
int col = cell.getColumnIndex();
int row = cell.getRowIndex();
String sheetName = cell.getSheet().getSheetName();
List<Name> result = new ArrayList<Name>();
for (int i=0; i<wb.getNumberOfNames(); i++) {
Name name = wb.getNameAt(i);
if (!sheetName.equals(name.getSheetName())) continue;
AreaReference area = new AreaReference(name.getRefersToFormula());
CellReference crList[] = area.getAllReferencedCells();
for (CellReference cr : crList) {
if (cr.getCol() == col
&& cr.getRow() == row) {
result.add(name);
continue;
}
}
}
return result;
}
}

Categories

Resources