Apache Poi setActiveCell() for multiple cells - java

I'm trying to use the method sheet.setActiveCell(CellAddress addr) to set a range of multiple cells active at the same time. I've tryed with multiple versions of Apache poi-ooxml library and now i'm using 3.16 which also supports the method sheet.setActiveCell(String addr)(I know 3.16 is old but the issue stays the same also with the latest version).
Following the suggestions on this question: Is it possible to set the active range with Apache POI XSSF?
I've managed to get it to work, both with the custom CellAddress and the String in the format "A1:B5".
The problem is that every time I try to open an xlsx in which a range of cells has been set to active using apache poi, I get an error message from Excel saying that the file is damaged and need to be recovered. If I do, the recovery completes correctly, but this error is annoying since I have to open a great number of these files each day.
Is there a way to avoid this error from excel (maybe modifying the creation of the xlsx or changing some setting in Excel)?

Only one cell can be the active cell. And Sheet.setActiveCell only sets that one active cell. So sheet.setActiveCell("A1:B5") will work if setActiveCell(String addr) is available but it leads to a corrupted sheet. That's why it was removed.
Multiple cells can be selected. But there are no methods to set the selected cells in apache poi's high level classes. So the underlying low level classes needs to be used. Doing this one needs differentiate between XSSF and HSSF because different low level classes needs to be used.
Following complete example sets active cell to B2. This also sets sheet view having selection and active cell to that one given cell B2. Then it uses low level methods of XSSF and HSSF to set the selection to B2:E5.
import java.io.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFSheet;
class CreateExcelSelectMultipleCells {
public static void main(String[] args) throws Exception {
try (Workbook workbook = new XSSFWorkbook(); FileOutputStream out = new FileOutputStream("Excel.xlsx") ) {
//try (Workbook workbook = new HSSFWorkbook(); FileOutputStream out = new FileOutputStream("Excel.xls") ) {
Sheet sheet = workbook.createSheet();
Row row;
Cell cell;
for (int r = 0; r < 6; r++) {
row = sheet.createRow(r);
for (int c = 0; c < 6; c++) {
cell = row.createCell(c);
cell.setCellValue("R" + (r+1) + "C" + (c+1));
}
}
// set active cell; this also sets sheet view having selection and active cell to one given cell
sheet.setActiveCell(new CellAddress("B2"));
// set selected cells
if (sheet instanceof XSSFSheet) {
XSSFSheet xssfSheet = (XSSFSheet) sheet;
xssfSheet.getCTWorksheet().getSheetViews().getSheetViewArray(0).getSelectionArray(0).setSqref(
java.util.Arrays.asList("B2:E5"));
} else if (sheet instanceof HSSFSheet) {
HSSFSheet hssfSheet = (HSSFSheet) sheet;
org.apache.poi.hssf.record.SelectionRecord selectionRecord = hssfSheet.getSheet().getSelection();
java.lang.reflect.Field field_6_refs = org.apache.poi.hssf.record.SelectionRecord.class.getDeclaredField("field_6_refs");
field_6_refs.setAccessible(true);
field_6_refs.set(
selectionRecord,
new org.apache.poi.hssf.util.CellRangeAddress8Bit[] { new org.apache.poi.hssf.util.CellRangeAddress8Bit(1,4,1,4) }
);
}
workbook.write(out);
}
}
}

Related

Renaming headers of XSSFTable with Apache Poi leads to corrupt XLSX-file

I am trying to rename headers of an existing xlsx-file. The idea is to have an excel-file to export data from XML to excel and reimport the XML once some user has made adjustments.
At the moment we have created a "template" xlsx-sheet with Excel which already contains a sortable table (XSSFTable in poi) and a mapping to a XSD-source. Then we import it via POI, map XML data into it and save it. To adjust the sheet to the users we want to translate the headers/column-names of this existing table into different languages. It worked with POI 3.10-FINAL but since an upgrade to 4.0.1 it leads to a corrupt xlsx-file when opening.
I found this question on stackoverflow already
Excel file gets corrupted when i change the value of any cell in the header (Columns Title)
but it is not answered and pretty old. But I tried to figure out what the comments may were about and tried to flatten the existing XSSFTable, copy the filled data to a new sheet and put on a new XSSFTable to the data. Sadly this seems to be pretty complicated so I am back to correcting the broken header-cells.
I also tried to create the whole sheet with POI and step away from using that "template"-xslx, but I cannot figure out how to implement our XSD-Mapping (in Excel its Developer-Tools -> Source -> Add and then mapping the nodes to some cells in a dynamic table)
The code that worked until the upgrade of poi is basically this:
//Sheet is the current XSSFSheet
//header is a Map with the original header-name from the template mapped to a the new translated name
//headerrownumber is the row containing the tableheader to be translated
public static void translateHeaders(Sheet sheet,final Map<String,String> header,int headerrownumber) {
CellRangeAddress address = new CellRangeAddress(headerrownumber,headerrownumber,0,sheet.getRow(headerrownumber).getLastCellNum()); //Cellrange is the header-row
MyCellWalk cellWalk = new MyCellWalk (sheet,address);
cellWalk.traverse(new CellHandler() {
public void onCell(Cell cell, CellWalkContext ctx) {
String val = cell.getStringCellValue();
if (header.containsKey(val)) {
cell.setCellValue(header.get(val));
}
}
});
}
MyCellWalk is a org.apache.poi.ss.util.cellwalk.CellWalk which traverses the cell range from top left to the bottom right cell.
As far as I could figure out its not enough to simply change the flat value of the cell because xlsx keeps references to the cellname in some of their maps, but I cannot figure out how to grab them all and rename the header. Maybe there is also another approach in translating the headernames?
Well, the XSSFTable.updateHeaders should do the trick if apache poi would not fail doing it.
All the following is done using apache poi 4.0.1.
I have downloaded your dummy_template.xlsx and then tried changing the table column headers in the sheet. But even after calling XSSFTable.updateHeaders the column names in the XSSFTable has not changed. So I had a look into XSSFTable.java -> updateHeaders to determine why this not happens. There we find:
if (row != null && row.getCTRow().validate()) {
//do changing the column names
}
So the column names only will be changed if the corresponding row in the sheet is valid XML according to Office Open XML name spaces. But in later Excel versions (after 2007) additional name spaces were added. In this case the row's XML looks like:
<row r="4" spans="1:3" x14ac:dyDescent="0.25">
Note the additional x14ac:dyDescent attribute. That's why row.getCTRow().validate() returns false.
The following code gets your dummy_template.xlsx, renames the column headers in the sheet and then calls a disarmed version static void updateHeaders(XSSFTable table). After that the result.xlsx is valid for opening in Excel.
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.ss.util.cellwalk.*;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.*;
import java.io.*;
import java.util.*;
class ExcelRenameTableColumns {
static void translateHeaders(Sheet sheet, final Map<String,String> header, int headerrownumber) {
CellRangeAddress address = new CellRangeAddress(
headerrownumber, headerrownumber,
0, sheet.getRow(headerrownumber).getLastCellNum());
CellWalk cellWalk = new CellWalk (sheet, address);
cellWalk.traverse(new CellHandler() {
public void onCell(Cell cell, CellWalkContext ctx) {
String val = cell.getStringCellValue();
if (header.containsKey(val)) {
cell.setCellValue(header.get(val));
}
}
});
}
static void updateHeaders(XSSFTable table) {
XSSFSheet sheet = (XSSFSheet)table.getParent();
CellReference ref = table.getStartCellReference();
if (ref == null) return;
int headerRow = ref.getRow();
int firstHeaderColumn = ref.getCol();
XSSFRow row = sheet.getRow(headerRow);
DataFormatter formatter = new DataFormatter();
System.out.println(row.getCTRow().validate()); // false!
if (row != null /*&& row.getCTRow().validate()*/) {
int cellnum = firstHeaderColumn;
CTTableColumns ctTableColumns = table.getCTTable().getTableColumns();
if(ctTableColumns != null) {
for (CTTableColumn col : ctTableColumns.getTableColumnList()) {
XSSFCell cell = row.getCell(cellnum);
if (cell != null) {
col.setName(formatter.formatCellValue(cell));
}
cellnum++;
}
}
}
}
public static void main(String[] args) throws Exception {
String templatePath = "dummy_template.xlsx";
String outputPath = "result.xlsx";
FileInputStream inputStream = new FileInputStream(templatePath);
Workbook workbook = WorkbookFactory.create(inputStream);
Sheet sheet = workbook.getSheetAt(0);
Map<String, String> header = new HashMap<String, String>();
header.put("textone", "Spalte eins");
header.put("texttwo", "Spalte zwei");
header.put("textthree", "Spalte drei");
translateHeaders(sheet, header, 3);
XSSFTable table = ((XSSFSheet)sheet).getTables().get(0);
updateHeaders(table);
FileOutputStream outputStream = new FileOutputStream(outputPath);
workbook.write(outputStream);
outputStream.close();
workbook.close();
}
}
If I open the dummy_template.xlsx using Excel 2007 and then save as dummy_template2007.xlsx, the row's XML changes to
<row r="4" spans="1:3">
Now when using this dummy_template2007.xlsx no manually calling the XSSFTable.updateHeaders is necessary. The XSSFTable.writeTo which is called by XSSFTable.commit does this automatically.

Why am I getting IllegalArgumentException while using copyRowFrom(...) in XSSFRow?

I'm trying to use copyRowFrom(...); to copy the first row of a spreadsheet to the first row of a new XSSFSheet but something is not working right.
You can find the XSSFRow class and the method here:
https://github.com/apache/poi/blob/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java#L581
I'm getting IllegalArgumentException("amountToMove must not be zero") from FormulaShifter.java:
https://github.com/apache/poi/blob/trunk/src/java/org/apache/poi/ss/formula/FormulaShifter.java#L80
It seems that the problem is at line 623 of XSSFRow where FormulaShifter.createForRowCopy(...) is invoked with parameter rowDifference = 0 because source row is 0 and destination row is 0:
https://github.com/apache/poi/blob/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java#L623
I don't know, maybe it's an error, but when reaching line 80 in FormulaShifter.java the parameter rowDifference corresponds to amountToMove which is 0 so it throws IllegalArgumentException.
Am I missing something or is this a bug of copyRowFrom(...); method in XSSFRow?
You are correct. This is a bug in XSSFRow since it calls FormulaShifter.createForRowCopy even if there is nothing to shift because destination row number is the same as source row number. You could file this as a bug to apache poi.
But then there is a need for creating a test case which can be provided there. I have done that for you. The code also provides a workaround. This is first copying to a wrong destination row where row number differs from source row number. Then it copies the wrong first destination row to the really needed destination row.
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
class ExcelCopyRowFrom {
public static void main(String[] args) throws Exception {
XSSFWorkbook workbook = (XSSFWorkbook)WorkbookFactory.create(new FileInputStream("SAMPLE.xlsx"));
XSSFSheet srcSheet = workbook.getSheetAt(0);
XSSFRow srcRow = srcSheet.getRow(0);
XSSFSheet destSheet = workbook.createSheet();
//XSSFRow destRow = destSheet.createRow(0); //this fails because destination row number is the same as source row number
XSSFRow destRow = destSheet.createRow(1); //this works
destRow.copyRowFrom(srcRow, new CellCopyPolicy());
//workaround copy wrong first destination row to really needed destination row
XSSFRow destRowNeeded = destSheet.createRow(0);
destRowNeeded.copyRowFrom(destRow, new CellCopyPolicy());
//the remove wrong first destination row
destSheet.removeRow(destRow);
FileOutputStream outputStream = new FileOutputStream("SAMPLENEW.xlsx");
workbook.write(outputStream);
outputStream.close();
workbook.close();
}
}
//fix amountToMove must not be zero:srcRows index base destStartRow+1,avoid the same
int indexAdd1 = 1;
for (Row row : failRowList) {
row.setRowNum(indexAdd1);
indexAdd1++;
}
failSheet.copyRows(failRowList,0, CellCopyPolicyFactory.newOnlyValue());

Java code to access data from an excel sheet and write that specific data to another excel sheet

I need to read data from a row of excel sheet through Java code and writing the content of it to another excel sheet but not in the same column but in different columns. Can anyone help me ? THANKS a lot in advance :)
Example: Suppose from the first excel file , I got this from one of the column : CN=user1,CN=Users,DC=example,DC=com
Now, I need to put this data in another excel sheet but in different columns i.e. each of the comma separated values will go to different columns.
You could try exporting to tab-delineated text document and manipulating that using Java code.
https://www.howtogeek.com/79991/convert-an-excel-spreadsheet-to-a-tab-delimited-text-file/
Just use Java's I/O capabilities from there.
https://docs.oracle.com/javase/tutorial/essential/io/
I've recently used the Apache POI library for parsing excel spreadsheets and found it incredibly useful.
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
void parseWorkbook(File file) {
POIFSFileSystem fs = new POIFSFileSystem(file.getInputStream());
HSSFWorkbook wb = new HSSFWorkbook(fs);
for (int i = 0; i < wb.getNumberOfSheets(); i++) {
parseSheet(wb.getSheetAt(i));
}
}
void parseSheet(HSSFSheet sheet) throws IllegalStateException {
final int rows = sheet.getPhysicalNumberOfRows();
HSSFRow row;
for (int r = 0; r < rows; r++) {
row = sheet.getRow(r);
if (row != null) {
parseRow(row);
}
}
}
void parseRow(HSSFRow row) {
row.getCell(0);
....
}
An example of reading and writing to a spreadsheet can be found here

Apache POI generate excel chart

All:
Pretty new to Apache POI and excel VBA, how can I implement something like this in Java with Apache POI or any other java lib:
VBA code in Excel:
Sheets("Sheet 1").ChartObjects("Line Chart")
.Chart.Axes(xlValue).MaximumScale = Sheets("Sheet 1").Range("A37")
I can not find any Chart related API working like this, any thought?
Another way around is: My goal is to use Java automatically update cell A37 and give its value as "Line Chart"'s xAxis Max scale, is there a way now to directly call this VBA code thru POI?
Thanks,
Answer for current latest stable version apache poi 3.17. Note XSSFChart is in development. So we should using XDDFChart for later versions.
You can get a List of XSSFCharts from the sheet's drawing via XSSFDrawing.getCharts. From that List get your needed XSSFChart. Then get that chart's axes via XSSFChart.getAxis. Then get the appropriate XSSFValueAxis from that List. And then change it's maximum via XSSFChartAxis.setMaximum.
Example:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.xssf.usermodel.charts.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
class ReadAndWriteExcelXSSFChart {
public static void main(String[] args) throws Exception {
Workbook workbook = WorkbookFactory.create(new FileInputStream("WBWithLineChart.xlsm"));
Sheet sheet = workbook.getSheetAt(0);
Row row = sheet.getRow(36); if (row == null) row = sheet.createRow(36);
Cell cell = row.getCell(0); if (cell == null) cell = row.createCell(0);
cell.setCellValue(10);
double valueA37 = cell.getNumericCellValue();
Drawing drawing = sheet.getDrawingPatriarch();
if (drawing instanceof XSSFDrawing) {
for (XSSFChart chart : ((XSSFDrawing)drawing).getCharts()) {
System.out.println(chart.getPackagePart().getPartName().getName());
if (chart.getPackagePart().getPartName().getName().endsWith("chart1.xml")) { //first chart in sheet
for (XSSFChartAxis axis : chart.getAxis()) { //all axes
System.out.println(axis);
if (axis instanceof XSSFValueAxis) { //value axis
axis.setMaximum(valueA37); // maximum = same value as in A37
System.out.println(axis.getMaximum());
}
}
}
}
}
workbook.write(new FileOutputStream("WBWithLineChart.xlsm"));
workbook.close();
}
}

How to update Excel sheet links using poi

I'm trying to get updated cell values after use setForceFormulaRecal method. But I'm getting still old values. Which is not actual result. If I opened Original file by clicking It will asking update Links dialogue box. If I click "ok" button then Its updating all cell formula result. So I want to update excel sheet links before its open by using poi. Please help in this situation.
//Before Setting values
HSSFCell cel2=row1.getCell(2);
HSSFCell cel4=row1.getCell(5);
cel2.setCellValue(690);
cel4.setCellValue(690);
wb.setForceFormulaRecalculation(true);
wb.write(stream);
//After Evaluatting the work book formulas I'm trying as follow
HSSFWorkbook wb = HSSFReadWrite.readFile("D://workspace//ExcelProject//other.xls");
HSSFSheet sheet=wb.getSheetAt(14);
HSSFRow row11=sheet.getRow(10);
System.out.println("** cell val: "+row11.getCell(3).getNumericCellValue());
I'm Also tried with Formula Evaluator But its showing errors As follow
Could not resolve external workbook name '\Users\asus\Downloads\??? & ???? ?????_091230.xls'. Workbook environment has not been set up.
at org.apache.poi.ss.formula.OperationEvaluationContext.createExternSheetRefEvaluator(OperationEvaluationContext.java:87)
at org.apache.poi.ss.formula.OperationEvaluationContext.getArea3DEval(OperationEvaluationContext.java:273)
at org.apache.poi.ss.formula.WorkbookEvaluator.getEvalForPtg(WorkbookEvaluator.java:660)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluateFormula(WorkbookEvaluator.java:527)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluateAny(WorkbookEvaluator.java:288)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluate(WorkbookEvaluator.java:230)
at org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.evaluateFormulaCellValue(HSSFFormulaEvaluator.java:351)
at org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.evaluateFormulaCell(HSSFFormulaEvaluator.java:213)
at org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.evaluateAllFormulaCells(HSSFFormulaEvaluator.java:324)
at org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.evaluateAll(HSSFFormulaEvaluator.java:343)
at HSSFReadWrite.readSheetData(HSSFReadWrite.java:85)
at HSSFReadWrite.main(HSSFReadWrite.java:346)
Caused by: org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment$WorkbookNotFoundException: Could not resolve external workbook name '\Users\asus\Downloads\??? & ???? ?????_091230.xls'. Workbook environment has not been set up.
at org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.getWorkbookEvaluator(CollaboratingWorkbooksEnvironment.java:161)
at org.apache.poi.ss.formula.WorkbookEvaluator.getOtherWorkbookEvaluator(WorkbookEvaluator.java:181)
at org.apache.poi.ss.formula.OperationEvaluationContext.createExternSheetRefEvaluator(OperationEvaluationContext.java:85)
... 11 more
OK, trying an answer:
First of all: Support for links to external workbooks is not included into the current stable version 3.10. So with this version it is not possible to evaluate such links directly. That's why evaluateAll() will fail for workbooks with links to external workbooks.
With Version 3.11 it will be possible to do so. But also only even if all the workbooks are opened and Evaluators for all the workbooks are present. See: http://poi.apache.org/apidocs/org/apache/poi/ss/usermodel/FormulaEvaluator.html#setupReferencedWorkbooks%28java.util.Map%29
What we can do with the stable version 3.10, is to evaluate all the cells which contains formulas which have not links to external workbooks.
Example:
The workbook "workbook.xlsx" contains a formula with a link to an external workbook in A2:
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Map;
import java.util.HashMap;
class ExternalReferenceTest {
public static void main(String[] args) {
try {
InputStream inp = new FileInputStream("workbook.xlsx");
Workbook wb = WorkbookFactory.create(inp);
Sheet sheet = wb.getSheetAt(0);
Row row = sheet.getRow(0);
if (row == null) row = sheet.createRow(0);
Cell cell = row.getCell(0);
if (cell == null) cell = row.createCell(0);
cell.setCellValue(123.45);
cell = row.getCell(1);
if (cell == null) cell = row.createCell(1);
cell.setCellValue(678.90);
cell = row.getCell(2);
if (cell == null) cell = row.createCell(2);
cell.setCellFormula("A1+B1");
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
//evaluator.evaluateAll(); //will not work because external workbook for formula in A2 is not accessable
System.out.println(sheet.getRow(1).getCell(0)); //[1]Sheet1!$A$1
//but we surely can evaluate single cells:
cell = wb.getSheetAt(0).getRow(0).getCell(2);
System.out.println(evaluator.evaluate(cell).getNumberValue()); //802.35
FileOutputStream fileOut = new FileOutputStream("workbook.xlsx");
wb.write(fileOut);
fileOut.flush();
fileOut.close();
} catch (InvalidFormatException ifex) {
} catch (FileNotFoundException fnfex) {
} catch (IOException ioex) {
}
}
}

Categories

Resources