I am using arraylist to collect reference IDs of the product from Excel sheet. I am using POI. The issue is that I don't want to include blank cells in my arraylist. Here is my code:
public static ArrayList<String> extractExcelContentByColumnIndex()
{
ArrayList<String> columnData=null;
try
{
FileInputStream fis=new FileInputStream(excel_path);
Workbook wb=WorkbookFactory.create(fis);
Sheet sh=wb.getSheet(sheetname);
Iterator<Row> rowIterator=sh.iterator();
columnData=new ArrayList<>();
while(rowIterator.hasNext())
{
Row row=rowIterator.next();
Iterator<Cell> cellIterator=row.cellIterator();
while(cellIterator.hasNext())
{
Cell cell=cellIterator.next();
if((row.getRowNum()>=3) && (row.getRowNum()<=sh.getPhysicalNumberOfRows()))
{
if(cell.getColumnIndex()==3)// column under watch
{
columnData.add(cell.getStringCellValue());
Collections.sort(columnData);
}
}
}
}
fis.close();
}
catch(Exception e)
{
e.printStackTrace();
}
System.err.println("DL BoM = "+columnData);
return columnData;
}
And output is :
DL BoM = [, , , , , , 03141, 03803, 08002, 08012, 08817, 13124, A9C22712, A9N21024, A9N21027, A9N21480]
The POI documentation provides some useful information to that specific topic.
Iterate over cells, with control of missing / blank cells
In some cases, when iterating, you need full control over how missing
or blank rows and cells are treated, and you need to ensure you visit
every cell and not just those defined in the file. (The CellIterator
will only return the cells defined in the file, which is largely those
with values or stylings, but it depends on Excel).
In cases such as these, you should fetch the first and last column
information for a row, then call getCell(int, MissingCellPolicy) to
fetch the cell. Use a MissingCellPolicy to control how blank or null
cells are handled.
// 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);
if (r == null) {
// This whole row is empty
// Handle it as needed
continue;
}
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
}
}
}
Source: http://poi.apache.org/spreadsheet/quick-guide.html#Iterator
Before getting the cell value.. check if it's not empty
if(cell.getColumnIndex()==3)// column under watch
{
if(cell.getStringCellValue().Trim() != "")
{
columnData.add(cell.getStringCellValue());
}
Collections.sort(columnData);
}
Related
I have very little knowledge of Apache POI. Is there any optimized way to delete the Excel rows quickly using Apache POI and Java?
It is taking more than two hours to remove the 11000 rows and is it possible to delete multiple rows at a time?
I have used the following code:
for (int s = rowNum; s < 11000; s++) {
Row r = sheet.getRow(s);
if(r != null) {
sheet.removeRow(r);
}
}
Unfortunately, as of apache-poi-5.0.0 it cannot be done any faster.
The problem of removing a row from an XSSFSheet may look linear but is actually quadratic.
So when you try to remove mutliple XSSFRows from an XSSFSheet, you are also removing the XSSFCells associated with it. Thus, more the number of XSSFCells the slower it gets.
Code from XSSFSheet:
/**
* Remove a row from this sheet. All cells contained in the row are removed as well
*
* #param row the row to remove.
*/
#Override
public void removeRow(Row row) {
if (row.getSheet() != this) {
throw new IllegalArgumentException("Specified row does not belong to this sheet");
}
// collect cells into a temporary array to avoid ConcurrentModificationException
ArrayList<XSSFCell> cellsToDelete = new ArrayList<>();
for (Cell cell : row) {
cellsToDelete.add((XSSFCell)cell);
}
for (XSSFCell cell : cellsToDelete) {
row.removeCell(cell);
}
final int rowNum = row.getRowNum();
// Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory
//noinspection UnnecessaryBoxing
final Integer rowNumI = Integer.valueOf(rowNum); // NOSONAR
// this is not the physical row number!
final int idx = _rows.headMap(rowNumI).size();
_rows.remove(rowNumI);
worksheet.getSheetData().removeRow(idx);
// also remove any comment located in that row
if(sheetComments != null) {
for (CellAddress ref : getCellComments().keySet()) {
if (ref.getRow() == rowNum) {
sheetComments.removeComment(ref);
}
}
}
}
My suggestion would be to copy the XSSFRows from the existing XSSFSheet to a new XSSFSheet and then remove the previous XSSFSheet. Use this iff, number of XSSFRows to be deleted is greater than the number of XSSFRows to be copied.
private void copyRowsAndDeleteSheet(XSSFWorkbook workbook) {
XSSFSheet srcSheet = workbook.createSheet();
// Inserting dummy data
for (int i = 0; i < 11000; i++) {
XSSFRow row = srcSheet.createRow(i);
row.createCell(0).setCellType(CellType.STRING);
row.getCell(0).setCellValue("Hello" + i);
}
// Sheet to be copied to
XSSFSheet destSheet = workbook.createSheet();
// Defines how the cell should be copied
CellCopyPolicy policy = new CellCopyPolicy().createBuilder().cellStyle(true).build();
for (int i = 0; i < 100; i++) {
// Row to be copied
XSSFRow srcRow = srcSheet.getRow(i);
// Inserting into a i+1 instead of i to avoid IllegalArgumentException throw by FormulaShifter
destSheet.createRow(i + 1).copyRowFrom(srcRow, policy);
}
// Shifting the rows up by 1 row to match source sheet
destSheet.shiftRows(1, 100, -1);
// removing the source sheet
workbook.removeSheetAt(workbook.getSheetIndex(srcSheet));
}
Note: copyRowFrom() is still beta so it would be wise to not use it in a production environment.
I have a requirement for apache poi to act like "pulling down" formatting in excel. So taking a sample row, getting the "formatting" in each cell and applying it to all the cells below. Formatting according to the requirement includes number formats and the cells' background colors changing depending on the value. So I wrote a class that gets the CellStyle from the example row's cells and applies it according.
public class FormatScheme implements ObjIntConsumer<Sheet> {
private Map<Integer, CellStyle> cellFormats = new LinkedHashMap<>();
public static FormatScheme of(Row row, int xOffset){
FormatScheme scheme = new FormatScheme();
for (int i = xOffset; i < row.getLastCellNum(); i++) {
Cell cell = row.getCell(i);
if(cell==null) continue;
scheme.cellFormats.put(i, cell.getCellStyle());
}
return scheme;
}
#Override
public void accept(Sheet sheet, int rowIndex) {
Row row = sheet.getRow(rowIndex);
if(row==null) row=sheet.createRow(rowIndex);
Row finalRow = row;
cellFormats.entrySet().forEach(entry -> {
Cell cell = finalRow.getCell(entry.getKey());
if(cell==null) cell= finalRow.createCell(entry.getKey());
cell.setCellStyle(entry.getValue());
});
}
private FormatScheme(){}
}
This does seem to work for the number formats but doesn't grab the changing background colors. ~I guess, I'm missing something.~
With help from Alex Richter I understand that I need to use the sheet's SheetConditionalFormatting. How can I get the ConditionalFormatting that are currently applied to a cell and expand the range the affect downward?
Your question is not really clear. But I suspect you want copying formatting from one target row to multiple adjacent following rows. And you want expanding the ranges of conditional formatting rules too, so that the cells in the adjacent following rows also follow that rules. So the same what Excel's format painter does if you select one row, then click format painter and then select multiple adjacent following rows.
How to copy cell styles, you have got already. But why doing this that complicated? Copying cell styles form one cell to another is a one-liner: targetCell.setCellStyle(sourceCell.getCellStyle());.
Second we should copy possible row style too. The following example has a method void copyRowStyle(Row sourceRow, Row targetRow) for this.
To expand the ranges for the conditional formatting rules we need getting the rules which are applied to the cell. The rules are stored on sheet level. So we need traversing the SheetConditionalFormatting to get the rules where the cell is in range. The following example has a method List<ConditionalFormatting> getConditionalFormattingsForCell(Cell cell) for this.
Having this we can expand the ranges of the conditional formatting rules. The following example has a method void expandConditionalFormatting(Cell sourceCell, Cell targetCell) for this. It expands the ranges of the conditional formatting rules from sourceCell to targetCell.
Complete example which shows the principle:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.usermodel.ConditionalFormatting;
import org.apache.poi.ss.util.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
import java.util.ArrayList;
class ExcelCopyFormatting {
static List<ConditionalFormatting> getConditionalFormattingsForCell(Cell cell) {
List<ConditionalFormatting> conditionalFormattingList = new ArrayList<ConditionalFormatting>();
Sheet sheet = cell.getRow().getSheet();
SheetConditionalFormatting sheetConditionalFormatting = sheet.getSheetConditionalFormatting();
for (int i = 0; i < sheetConditionalFormatting.getNumConditionalFormattings(); i++) {
ConditionalFormatting conditionalFormatting = sheetConditionalFormatting.getConditionalFormattingAt(i);
CellRangeAddress[] cellRangeAddressArray = conditionalFormatting.getFormattingRanges();
for (CellRangeAddress cellRangeAddress : cellRangeAddressArray) {
if (cellRangeAddress.isInRange(cell)) {
conditionalFormattingList.add(conditionalFormatting);
}
}
}
return conditionalFormattingList;
}
static void expandConditionalFormatting(Cell sourceCell, Cell targetCell) {
List<ConditionalFormatting> conditionalFormattingList = getConditionalFormattingsForCell(sourceCell);
for (ConditionalFormatting conditionalFormatting : conditionalFormattingList) {
CellRangeAddress[] cellRangeAddressArray = conditionalFormatting.getFormattingRanges();
for (int i = 0; i < cellRangeAddressArray.length; i++) {
CellRangeAddress cellRangeAddress = cellRangeAddressArray[i];
if (cellRangeAddress.isInRange(sourceCell)) {
if (cellRangeAddress.getFirstRow() > targetCell.getRowIndex()) {
cellRangeAddress.setFirstRow(targetCell.getRowIndex());
}
if (cellRangeAddress.getFirstColumn() > targetCell.getColumnIndex()) {
cellRangeAddress.setFirstColumn(targetCell.getColumnIndex());
}
if (cellRangeAddress.getLastRow() < targetCell.getRowIndex()) {
cellRangeAddress.setLastRow(targetCell.getRowIndex());
}
if (cellRangeAddress.getLastColumn() < targetCell.getColumnIndex()) {
cellRangeAddress.setLastColumn(targetCell.getColumnIndex());
}
cellRangeAddressArray[i] = cellRangeAddress;
}
}
conditionalFormatting.setFormattingRanges(cellRangeAddressArray);
}
}
static void copyRowStyle(Row sourceRow, Row targetRow) {
if (sourceRow.isFormatted()) {
targetRow.setRowStyle(sourceRow.getRowStyle());
}
}
static void copyCellStyle(Cell sourceCell, Cell targetCell) {
targetCell.setCellStyle(sourceCell.getCellStyle());
}
static void copyFormatting(Sheet sheet, int fromRow, int upToRow) {
Row sourceRow = sheet.getRow(fromRow);
for (int r = fromRow + 1; r <= upToRow; r++) {
Row targetRow = sheet.getRow(r);
if (targetRow == null) targetRow = sheet.createRow(r);
copyRowStyle(sourceRow, targetRow);
for (Cell sourceCell : sourceRow) {
Cell targetCell = targetRow.getCell(sourceCell.getColumnIndex());
if (targetCell == null) targetCell = targetRow.createCell(sourceCell.getColumnIndex());
copyCellStyle(sourceCell, targetCell);
if (r == upToRow) {
if (getConditionalFormattingsForCell(sourceCell).size() > 0) {
expandConditionalFormatting(sourceCell, targetCell);
}
}
}
}
}
public static void main(String[] args) throws Exception {
//Workbook workbook = WorkbookFactory.create(new FileInputStream("./Workbook.xls")); String filePath = "./WorkbookNew.xls";
Workbook workbook = WorkbookFactory.create(new FileInputStream("./Workbook.xlsx")); String filePath = "./WorkbookNew.xlsx";
Sheet sheet = workbook.getSheetAt(0);
copyFormatting(sheet, 1, 9); // copy formatting from row 2 up to row 10
FileOutputStream out = new FileOutputStream(filePath);
workbook.write(out);
out.close();
workbook.close();
}
}
In my code I am going through an XLSX-file row by row, validating them against a database with Apache POI 4.1.0. If I find a incorrect row I will "mark" them for deletion by adding it to the the List<XSSFRow> toRemove. After iterating over every row this small method is supposed to remove the rows marked for deletion:
ListIterator<XSSFRow> rowIterator = toRemove.listIterator(toRemove.size());
while (rowIterator.hasPrevious()) {
XSSFRow row = rowIterator.previous();
if (row != null && row.getSheet() == sheet) {
int lastRowNum = sheet.getLastRowNum();
int rowIndex = row.getRowNum();
if (rowIndex == lastRowNum) {
sheet.removeRow(row);
} else if (rowIndex >= 0 && rowIndex < lastRowNum) {
sheet.removeRow(row);
} else {
System.out.println("\u001B[31mERROR: Removal failed because row " + rowIndex + " is out of bounds\u001B[0m");
}
System.out.println("Row " + rowIndex + " successfully removed");
} else {
System.out.println("Row skipped in removal because it was null already");
}
}
But for some unknown reason it removes all rows perfectly and then throws a XmlValueDisconnectedException when getting the row index (getRowNum()) of the last (first added) row.
Relevant part of the Stacktrace:
org.apache.xmlbeans.impl.values.XmlValueDisconnectedException
at org.apache.xmlbeans.impl.values.XmlObjectBase.check_orphaned(XmlObjectBase.java:1258)
at org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTRowImpl.getR(Unknown Source)
at org.apache.poi.xssf.usermodel.XSSFRow.getRowNum(XSSFRow.java:400)
at Overview.removeRows(Overview.java:122)
EDIT: I also tried changing the iteration process (see below) but the error stays the same.
for (XSSFRow row : toRemove) {
// same code as above without iterator and while
}
The error occurs if one row is double contained in List toRemove. A List allows duplicate entries. So the same row may be double added to the List. If then Iterator gets the first occurrence of that row and this will be removed properly from the sheet. But then if the same row occurs again later, the row.getRowNum() fails that way because the row does not more exists in the sheet.
Here is complete code to reproduce that behavior:
import org.apache.poi.ss.usermodel.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.*;
public class ExcelRemoveRows {
public static void main(String[] args) throws Exception {
String filePath = "Excel.xlsx"; // must contain at least 5 filled rows
Workbook workbook = WorkbookFactory.create(new FileInputStream(filePath));
Sheet sheet = workbook.getSheetAt(0);
List<Row> toRemoveList = new ArrayList<Row>();
toRemoveList.add(sheet.getRow(0));
toRemoveList.add(sheet.getRow(2));
toRemoveList.add(sheet.getRow(4));
toRemoveList.add(sheet.getRow(2)); // this produces the error
System.out.println(toRemoveList); // contains row hawing index 2 (r="3") two times
for (Row row : toRemoveList) {
System.out.println(row.getRowNum()); // XmlValueDisconnectedException on second occurance of row index 2
sheet.removeRow(row);
}
FileOutputStream out = new FileOutputStream("Changed"+filePath);
workbook.write(out);
out.close();
workbook.close();
}
}
The solution is to avoid that the List contains the same row multiple times.
I would not collecting the rows to remove in a List<XSSFRow> but the row numbers to remove in a Set<Integer>. That would avoid duplicates since a Set does not allow duplicate elements. The row to remove then can simply got via sheet.getRow(rowNum).
Code:
...
Set<Integer> toRemoveSet = new HashSet<Integer>();
toRemoveSet.add(sheet.getRow(0).getRowNum());
toRemoveSet.add(sheet.getRow(2).getRowNum());
toRemoveSet.add(sheet.getRow(4).getRowNum());
toRemoveSet.add(sheet.getRow(2).getRowNum());
System.out.println(toRemoveSet); // does not contain the row index 2 two times
for (Integer rowNum : toRemoveSet) {
Row row = sheet.getRow(rowNum);
System.out.println(row.getRowNum());
sheet.removeRow(row);
}
...
I am looking to get the value of the selected row in an AbstractTableModel and I am noticing some things. It is correctly reporting what sell (row) I am on, when it is selected, but as soon as I click my button to remove, the selected row value goes to 0. Resulting in the 0 row always being removed. I want to get the value int selectedRow and use it to remove it from the table and my ArrayLists.
ListSelectionModel rsm = table.getSelectionModel();
ListSelectionModel csm = table.getColumnModel().getSelectionModel();
csm.addListSelectionListener(new SelectionDebugger(columnCounter,csm));
columnCounter = new JLabel("(Selected Column Indices Go Here)");
columnCounter.setBounds(133, 62, 214, 14);
csm.addListSelectionListener(new SelectionDebugger(columnCounter,csm));
contentPane1.add(columnCounter);
rowCounter = new JLabel("(Selected Column Indices Go Here)");
rowCounter.setBounds(133, 36, 214, 14);
rsm.addListSelectionListener(new SelectionDebugger(rowCounter, rsm));
contentPane1.add(rowCounter);
SelectionDebugger:
public class SelectionDebugger implements ListSelectionListener {
JLabel debugger;
ListSelectionModel model;
public SelectionDebugger(JLabel target, ListSelectionModel lsm) {
debugger = target;
model = lsm;
}
public void valueChanged(ListSelectionEvent lse) {
if (!lse.getValueIsAdjusting()) {
// skip all the intermediate events . . .
StringBuffer buf = new StringBuffer();
int[] selection = getSelectedIndices(model.getMinSelectionIndex(),
model.getMaxSelectionIndex());
if (selection.length == 0) {
buf.append("none");
//selectedRow = buf.toString();
}
else {
for (int i = 0; i < selection.length -1; i++) {
buf.append(selection[i]);
buf.append(", ");
}
buf.append(selection[selection.length - 1]);
}
debugger.setText(buf.toString());
System.out.println("CampaignConfiguration: Selected Row: " + selection[selection.length - 1]);
// Set the selected row for removal;
selectedRow = selection[selection.length - 1];
}
}
// This method returns an array of selected indices. It's guaranteed to
// return a nonnull value.
protected int[] getSelectedIndices(int start, int stop) {
if ((start == -1) || (stop == -1)) {
// no selection, so return an empty array
return new int[0];
}
int guesses[] = new int[stop - start + 1];
int index = 0;
// manually walk through these . . .
for (int i = start; i <= stop; i++) {
if (model.isSelectedIndex(i)) {
guesses[index++] = i;
}
}
// ok, pare down the guess array to the real thing
int realthing[] = new int[index];
System.arraycopy(guesses, 0, realthing, 0, index);
return realthing;
}
}
}
The TableModel has nothing to do with selection. The View(JTable) is responsible for the selection.
I want to get the value int selectedRow and use it to remove it from the table and my ArrayLists.
You should NOT have separate ArrayLists. The data should only be contained in the TableModel.
If you want to delete a row from the table (and the TableModel) then you can use the getSelectedIndex() method of the table in your ActionListener added to the "Delete" button. Something like:
int row = table.getSelectedIndex();
if (row != -1)
{
int modelRow = table.convertRowIndexToModel( row );
tableModel.removeRow( modelRow );
}
If you are not using the DefaultTableModel, then your custom TableModel will need to implement the "removeRow(...)" method.
I want to make a table in a view part which will be dynamically updated (row count, column count, column name). The model is a collection of string array. Can I do that?
The easiest way to do that would be to use a TableViewer instance, specify its content provider and then, you can call its refresh() method every time a change has been made to its content.
#itun. Simplest way to show columns dynamically is to create all columns and set the width of unnecessary columns to zero.
There is another way (which I am using) to create dynamic columns is as follow.
Create Extension Point for columns (column name,orientation,data provider)
Create Extension of by using step 1.
Implementation of creating columns in View.
String[] id = preferences.getString(PREFS_COLUMNS).split(";");
for (int i = 0; i < id.length; i++) {
String name = "";
int style = SWT.LEFT;
Image image = null;
ILabelProvider provider = registry.createLabelProvider(id[i]);
if (provider != null) {
name = registry.getName(id[i]);
style = registry.getOrientation(id[i]);
if (provider instanceof ITableLabelProvider) {
name = ((ITableLabelProvider) provider).getColumnText(null, i);
image = ((ITableLabelProvider) provider).getColumnImage(null, i);
}
} else {
LogFactory.getLog(getClass()).warn("Missing column [" + id[i] + "]");
}
if (index < table.getColumnCount()) {
tableColumn = table.getColumn(index);
if (tableColumn.getData("labelProvider") != null)
((ILabelProvider) tableColumn.getData("labelProvider")).dispose();
} else {
tableColumn = new TableColumn(table, style);
tableColumn.addControlListener(columnControlListener);
tableColumn.addSelectionListener(columnSelectionListener);
tableColumn.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
if (e.widget.getData("labelProvider") != null)
((ILabelProvider) e.widget.getData("labelProvider")).dispose();
}
});
}
tableColumn.setText(name);
tableColumn.setAlignment(style);
tableColumn.setImage(image);
tableColumn.setMoveable(true);
tableColumn.setData("labelProvider", provider);
tableColumn.setData("columnId", id[i]);
index++;
}