I need convert HashMaps to xlsx using poi. For sheet data2 i need something like that:
table1:
But i have table2:
Here's my list of HashMaps:
rows=[{kol2=s, kol1=s}, {kol2=bbbb, kol3=bbbb, kol1=aaaa}, {kol2=bbbb, kol3=bbbb, kol1=aaaa}, {kol2=bbbb, kol3=bbbb, kol1=aaaa}, {kol2=s, kol1=s}]}
Here's my code:
XSSFWorkbook workBook = new XSSFWorkbook();
XSSFSheet sheet = workBook.createSheet("data");
XSSFSheet sheet2 = workBook.createSheet("data2");
int rowCount = 0;
int help = 1;
List<HashMap<String, Object>> rows = ((List<HashMap<String, Object>>) x);
int rowCount2 = 0;
int header = 1;
Row header2 = sheet2.createRow(0);
for (int i = 0; i < rows.size(); i++) {
int li = 0;
Row row2 = sheet2.createRow(++rowCount2);
HashMap<String, Object> row = rows.get(i);
int columnCount2 = 0;
for (HashMap.Entry<String, Object> subElement : row.entrySet()) {
if (subElement.getValue() != null) {
if (i == li) {
Cell cell = header2.createCell(header);
cell.setCellValue(subElement.getKey().toString());
header++;
}
li++;
Cell cell2 = row2.createCell(++columnCount2);
cell2.setCellValue(subElement.getValue().toString());
}
}
}
Someone can help?
Iterating over a HashMap's EntrySet
The first problem is that you are iterating over the entrySet of your HashMap
for (HashMap.Entry<String, Object> subElement : row.entrySet()) {
// no guaranteed order
}
Looking at the JavaDoc of the Set#iterator() method you will see this:
Returns an iterator over the elements in this set. The elements are returned in no particular order (unless this set is an instance of some class that provides a guarantee).
There are Sets which are ordered (such as the TreeSet), but since you are using a HashMap, your EntrySet won't be ordered too.
Notice the column order in your sheet is kol2-kol3-kol1. Don't you want it to be kol1-kol2-kol3?
Not creating empty columns
You are forgetting to create empty cells for columns you don't have in your Map.
if (subElement.getValue() != null) {
// there won't be an empty cell if you e.g. don't have kol2 in your rows Map,
// since this just skips your current value
}
This is why you end up with something like:
kol2 kol3 kol1
s s
bbbb bbbb aaaa
...
instead of:
kol2 kol3 kol1
s s
bbbb bbbb aaaa
...
Creating the header row inside the loop
By creating the header row inside your loop, you are making your solution more complicated than necessary. It would be much easier just to create the header row and then loop over your entries in the List.
if (i == li) {
Cell cell = header2.createCell(header);
cell.setCellValue(subElement.getKey().toString());
header++;
}
If you are doing this outside the loop, there is no need for the li and the header variable
Suggested solution
I would (for a start) come up with something like this (I added some extra comments I normally wouldn't put there to make more clear what the intentions are and what aspects of the solution you need to understand):
XSSFSheet sheet2 = workBook.createSheet("data2");
List<HashMap<String, Object>> rows = ((List<HashMap<String, Object>>) x);
List<String> headers = Arrays.asList("kol1", "kol2", "kol3");
int currentRowNumber = 0;
// create header row
Row header = sheet2.createRow(currentRowNumber);
for (int i = 0; i < headers.size(); i++) {
Cell headerCell = header.createCell(i);
headerCell.setCellValue(headers.get(i));
}
// create data rows (we loop over the rows List)
for (int i = 0; i < rows.size(); i++) {
HashMap<String, Object> row = rows.get(i);
// we neet to increment the rowNumber for the row in the sheet at the beginning of
// each row. entry 0 in the rows List is in sheetRow 1, entry 1 in sheetRow 2, etc.
currentRowNumber++;
Row sheetRow = sheet2.createRow(currentRowNumber);
// we can now loop over the columns inside the row loop (using the headers List)
// we create a Cell for each column, but only fill it if there is
for (int j = 0; j < headers.size(); j++) {
Cell cell = sheetRow.createCell(j);
// only fill the cell if we are having data in the row map for the current column
String currentColumnName = headers.get(j);
if (row.containsKey(currentColumnName)) {
cell.setCellValue(row.get(currentColumnName).toString());
}
}
}
If you want a different column order, just change the header List and you are done (e.g. Arrays.asList("kol2", "kol3", "kol1")).
Related
I am filtering a file rows with an if condition and I need to make a new array which containing only the values that satisfy the condition.
I can get them in the console but I don't know how to assign them to a table, can anyone help me please?
String section_title;
for (int j=11; j<row_num; j++)
{
Row row = (Row) rowIterator.next();
Cell s0 = sheet.getRow(j-1).getCell(0);
if(s0.toString()!="" )
{
section_title = s0.toString();
d = j-1;
System.out.println(d);
}
}
Consider using an arraylist object to hold those values.
It would look something like this:
String section_title;
ArrayList<int> list = new ArrayList<int>(); // instanciate the array list
for (int j=11; j<row_num; j++)
{
Row row = (Row) rowIterator.next();
Cell s0 = sheet.getRow(j-1).getCell(0);
if(s0.toString()!="" )
{
section_title = s0.toString();
d = j-1;
list.add(d); // Add d to the list
}
}
System.out.println(list); // Print the final list
The goal is to scan some input and to get an array of int as a result.
As we do not know the final number of entries for the array, we should use something with a variable length for that purpose. Usually, this would be a List of some kind, but List is not defined for int (or other primitives), only for object types like Integer. So we would need a transformation step after collecting the numbers.
Since Java 8, we have Streams, and with that, the problem can be solved more directly:
String section_title;
var builder = IntStream.builder();
for( var j = 11; j < row_num; ++j )
{
var row = (Row) rowIterator.next();
Cell s0 = sheet.getRow( j - 1 ).getCell( 0 );
if( !s0.toString().isEmpty() )
{
section_title = s0.toString();
builder.add( Integer.valueOf( j - 1 ) );
}
}
var array = builder.build().toArray();
System.out.println( array );
Under the hood, this solution may end up to be nearly the same as one using Collections. But it still looks neater.
I am stuck with my loop where I want to loop the arraylist of string that contain data like this
[A,B,C,D,E,F]
where I want to loop the arraylist at the top row only like the image down below. The result that I get when I run the code here is the the same as the first image.
Row roww1 = sheet.createRow(0);
for (int i = 0; i < shiftname.size(); i++) {
Cell toprow = roww1.createCell(i + 4);
toprow.setCellValue(shiftname.get(i));
}
This is the outcome that I want it to be
I have a method for reading Excel cells using Apache POI, and it works fine. Well... almost fine.
public static ArrayList readXLsXFile() throws FileNotFoundException, IOException {
ArrayList outListaExcel = new ArrayList();
FileInputStream fis;
ptxf= new FileInputStream(pathToExcelFile);
XSSFWorkbook workbook = new XSSFWorkbook(ptxf);
XSSFSheet sheetAr = workbook.getSheetAt(0);
Iterator rowsAr = sheetAr.rowIterator();
while (rowsAr.hasNext()) {
XSSFRow row1 = (XSSFRow) rowsAr.next();
Iterator cellsAr = row1.cellIterator();
ArrayList<String> arr;
arr = new ArrayList();
while (cellsAr.hasNext()) {
XSSFCell cell1 = (XSSFCell) cellsAr.next();
arr.add(String.valueOf(cell1));
}
outListaExcel.add(arr);
}
return outListaExcel;
}
If cells are formatted, for example if whole A column have borders, then it will keep reading empty cells giving me empty strings. How to ignore those empty(formated) cells?
So readXLsXFile will give me an ArryList with
[0] -> [1][2]
[1] -> [3][4]
But it will also give ten more nodes with empty strings,because coloumn A is formated with borders.
edit after Gagravarr answer.
I can avoid checking wether subList is empty and then do not add it to mainList. But in the case of some very large .xls files and if there is many of them it will take too long, and generaly I think it is not a good practice.
My question was if there is something for rows, like it is for cells that I have overlooked.
ArrayList<ArrayList<String>>mainLista = new ArrayList<ArrayList<String>>();
for (int rowNum = rowStart; rowNum < rowEnd; rowNum++) {
Row r = sheet.getRow(rowNum);
int lastColumn = r.getLastCellNum();
ArrayList<String> subList = new ArrayList<String>();
for (int cn = 0; cn < lastColumn; cn++) {
Cell c = r.getCell(cn, Row.RETURN_BLANK_AS_NULL);
if (c != null) {
subList.add(c.getStringCellValue());
} else {
}
}
if (!subList.isEmpty() ){ // I think it is not good way
mainLista.add(subList);} // to do this, because it still reads
} // an empty rows
As explained in the Apache POI Documentation on Iterate over rows and cells, the iterators only give you the rows and cells which are defined and have/had content.
If you want to fetch cells with full control over blank or empty cells, you need to instead use something like:
// Decide which rows to process
int rowStart = Math.min(15, sheet.getFirstRowNum());
int rowEnd = Math.max(1400, sheet.getLastRowNum());
for (int rowNum = rowStart; rowNum < rowEnd; rowNum++) {
Row r = sheet.getRow(rowNum);
int lastColumn = Math.max(r.getLastCellNum(), MY_MINIMUM_COLUMN_COUNT);
for (int cn = 0; cn < lastColumn; cn++) {
Cell c = r.getCell(cn, Row.RETURN_BLANK_AS_NULL);
if (c == null) {
// The spreadsheet is empty in this cell
} else {
// Do something useful with the cell's contents
}
}
}
If you want to fetch blank cells (typically those with styling but no values), play with the other Missing Cell Policies, eg RETURN_NULL_AND_BLANK
set the border for column B, in my case it helped me
I use the following code in order to autosize columns in my spreadsheet:
for (int i = 0; i < columns.size(); i++) {
sheet.autoSizeColumn(i, true);
sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 600);
}
The problem is it takes more than 10 minutes to autosize each column in case of large spreadsheets with more than 3000 rows. It goes very fast for small documents though. Is there anything which could help autosizing to work faster?
Solution which worked for me:
It was possible to avoid merged regions, so I could iterate through the other cells and finally autosize to the largest cell like this:
int width = ((int)(maxNumCharacters * 1.14388)) * 256;
sheet.setColumnWidth(i, width);
where 1.14388 is a max character width of the "Serif" font and 256 font units.
Performance of autosizing improved from 10 minutes to 6 seconds.
The autoSizeColumn function itself works not perfect and some columns width not exactly fit the data inside. So, I found some solution that works for me.
To avoid crazy calculations let give that to autoSizeColumn() function:
sheet.autoSizeColumn(<columnIndex>);
Now, our column autosized by library but we wont to add a little bit more to the current column width to make table looks fine:
// get autosized column width
int currentColumnWidth = sheet.getColumnWidth(<columnIndex>);
// add custom value to the current width and apply it to column
sheet.setColumnWidth(<columnIndex>, (currentColumnWidth + 2500));
The full function could looks like:
public void autoSizeColumns(Workbook workbook) {
int numberOfSheets = workbook.getNumberOfSheets();
for (int i = 0; i < numberOfSheets; i++) {
Sheet sheet = workbook.getSheetAt(i);
if (sheet.getPhysicalNumberOfRows() > 0) {
Row row = sheet.getRow(sheet.getFirstRowNum());
Iterator<Cell> cellIterator = row.cellIterator();
while (cellIterator.hasNext()) {
Cell cell = cellIterator.next();
int columnIndex = cell.getColumnIndex();
sheet.autoSizeColumn(columnIndex);
int currentColumnWidth = sheet.getColumnWidth(columnIndex);
sheet.setColumnWidth(columnIndex, (currentColumnWidth + 2500));
}
}
}
}
P.S. Thanks Ondrej Kvasnovsky for the function https://stackoverflow.com/a/35324693/13087091
The autosizeColumn() function very slow and unneficient. Even authors of apache POI mentioned in docs, that:
This process can be relatively slow on large sheets, ...
Calculating and setting the cell's width manually is way faster - in my case I reduced the time from ~25,000ms to ~1-5ms.
This is how to achieve it (I was basing on Vladimir Shcherbukhin's answer:
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet();
final int[] maxNumCharactersInColumns = new int[headers.length]; // maximum number of characters in columns. Necessary to calculate the cell width in most efficient way. sheet.autoSizeColumn(...) is very slow.
Row headersRow = sheet.createRow(0);
CellStyle headerStyle = createHeadersStyle(workbook); // createHeadersStyle() is my own function. Create headers style if you want
for (int i = 0; i < headers.length; i++) { // create headers
Cell headerCell = headersRow.createCell(i, CELL_TYPE_STRING);
headerCell.setCellValue(headers[i]);
headerCell.setCellStyle(headerStyle);
int length = headers[i].length();
if (maxNumCharactersInColumns[i] < length) { // adjust the columns width
maxNumCharactersInColumns[i] = length + 2; // you can add +2 if you have filtering enabled on your headers
}
}
int rowIndex = 1;
for (List<Object> rowValues : rows) {
Row row = sheet.createRow(rowIndex);
int columnIndex = 0;
for (Object value : rowValues) {
Cell cell = createRowCell(row, value, columnIndex); // createRowCell() is my own function.
int length;
if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
String cellValue = cell.getStringCellValue();
// this is quite important part. In some excel spreadsheet you can have a values with line-breaks. It'll be cool to handle that scenario :)
String[] arr = cellValue.split("\n"); // if cell contains complex value with line breaks, calculate only the longest line
length = Arrays.stream(arr).map(String::length).max(Integer::compareTo).get();
} else {
length = value != null ? value.toString().length() : 0;
}
if (maxNumCharactersInColumns[columnIndex] < length) { // if the current cell value is the longest one, save it to an array
maxNumCharactersInColumns[columnIndex] = length;
}
columnIndex++;
}
rowIndex++;
}
for (int i = 0; i < headers.length; i++) {
int width = (int) (maxNumCharactersInColumns[i] * 1.45f) * 256; // 1.45f <- you can change this value
sheet.setColumnWidth(i, Math.min(width, MAX_CELL_WIDTH)); // <- set calculated cell width
}
sheet.setAutoFilter(new CellRangeAddress(0, 0, 0, headers.length - 1));
ByteArrayOutputStream output = new ByteArrayOutputStream();
workbook.write(output);
workbook.close();
Unfortunately I don't have enough reputations yet to add comments in answers. So here some annotations:
When using Row row = sheet.getRow(sheet.getFirstRowNum()); be shure, this row contains at least a value in the last column. Otherwise the cellIterator will end too early, i.e. if a subsequent row has a value in this column, this column will not be autosized. This problem is bypassed if rowcontains the headers (names of the columns). Or explicit use a known header row, e.g.
int indexOfHeaderRow = ...;
...
Row row = sheet.getRow(indexOfHeaderRow);
Jakub SÅ‚owikowski
sheet.setColumnWidth(i, Math.min(width, MAX_CELL_WIDTH)); // <- set calculated cellwidth
I'm not shure about this line because there is no information about content of MAX_CELL_WIDTH - perhaps overall maximum? So I used instead:
sheet.setColumnWidth(i, Math.max(width, 2048));
2048 seams to be the default width? This value prevents extremely narrow widths for empty columns.
I'm writing a program to read a large xml file and create an excel file from it. The attributes of each node will be the column headers in excel file. I created a Dom object and got the nodelist. I need to iterate through it and for each node, i need to add a row in excel sheet with the node's attributes values as column values. So, when iterating, i need to create rows dynamically. How can i do it? I dont see a functionality to add created rows in apache POI, so far what i have seen is to define new rows everytime. I'm unable to do it since it has more than 5000 entries. Basically what i want to do is:
Node node = null;
HSSFRow datarow = null;
for (int i = 0; i < nodeList.getLength(); i++) {
node = nodeList.item(i);
datarow = spreadSheet.createRow(i);
//set values for data row here, and add it.
//so in the loop, next time the same variable will be assigned to spreadSheet.createRow(1) etc.
}
I understand that the createRow is invoked from spreadSheet, which will add the row to it. But in the loop, the same variable will be assigned to other rows too, so i think finally i will get only 1 row. Please advice me on this.
Try the following
Node node = null;
HSSFRow datarow = null;
for (int i = 0; i < nodeList.getLength(); i++) {
// On each loop you get the value of node item
node = nodeList.item(i);
//For every new node list you will create a row
datarow = spreadSheet.createRow(i);
//Finally set the node value to the columns of the newly created Row
}
Hope this helps !!
createRow has already created the row in the worksheet and is returning a reference to the newly created row. You will lose this reference on the next loop iteration but it will not remove/override the previous row from the worksheet. You can expect to have the correct number of rows in the end.
int totalRows = 5;
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("Data");
HSSFRow datarow = null;
for (int i = 0; i <= totalRows; i++) {
datarow = sheet.createRow(i);
datarow.createCell(0).setCellValue(Integer.toString(i));
}
System.out.println("Total Rows: " + sheet.getLastRowNum());
System.out.println("First row cell value: " + sheet.getRow(0).getCell(0).getStringCellValue());
System.out.println("Last row cell value: " + sheet.getRow(totalRows).getCell(0).getStringCellValue());
/*
Total rows: 5
First row cell value: 0
Last row cell value: 5
*/