3

I'm trying to copy from cells from one workbook to another with the latest version of Apache POI (4.1.2).

If both workbooks are .xlsx files, everything works fine. But if the source workbook is an (old) .xls file and the destination workbook is an .xlsx file, the following code fails

// Copy style from old cell and apply to new cell
CellStyle newCellStyle = targetWorkbook.createCellStyle();
newCellStyle.cloneStyleFrom(sourceCell.getCellStyle());
targetCell.setCellStyle(newCellStyle);

The exception that's thrown is:

java.lang.IllegalArgumentException: Can only clone from one XSSFCellStyle to another, not between HSSFCellStyle and XSSFCellStyle

If we can't use cloneStyleFrom when the files (or Workbook objects) are of different types, how can we convert a HSSFCellStyle object to a XSSFCellStyle?

Dónal
  • 185,044
  • 174
  • 569
  • 824

1 Answers1

5

The answer to your question "How can we convert a HSSFCellStyle object to a XSSFCellStyle?" is: We can't do that using apache poi 4.1.2. This simply is not supported as clearly stated in CellStyle.cloneStyleFrom: "However, both of the CellStyles will need to be of the same type (HSSFCellStyle or XSSFCellStyle)."

The other question is: Should we at all convert one cell style into another? Or what use cases are there for CellStyle.cloneStyleFrom at all? In my opinion there are none. There are Excel limitations for the count of unique cell formats/cell styles. See Excel specifications and limits. So we should not create a single cell style for each single cell because then those limitations will be reached very fast. So instead of cloning cell styles we should get the style properties from source style style1 and then using CellUtil.setCellStyleProperties to set those style properties to the other cell in question. This method attempts to find an existing CellStyle that matches the cell's current style plus styles properties in properties. A new style only is created if the workbook does not contain a matching style.

Since your question title is "Copy cells between Excel workbooks with Apache POI", I have created a working draft of how I woud do this.

The following code first gets a existent Workbook.xls as HSSFWorkbook wb1 and creates a new XSSFWorkbook wb2. Then it loops over all cells of the first sheet of wb1 and tries copying those cells into the first sheet of wb2. To do so there is a method copyCells(Cell cell1, Cell cell2) which uses copyStyles(Cell cell1, Cell cell2). The latter gets the style properties from source style style1 got from cell1 and then uses CellUtil.setCellStyleProperties to set those style properties to cell2. For copying fonts copyFont(Font font1, Workbook wb2) is used. This tries creating new fonts in wb2 only if such a font is not already present in that workbook. This is necessary because there also is a limit of unique font types per workbook in Excel.

Working example:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.util.CellUtil;

import java.io.FileInputStream;
import java.io.FileOutputStream;

import java.util.*;

class ExcelCopyCells {

 static Font copyFont(Font font1, Workbook wb2) {
  boolean isBold = font1.getBold();
  short color = font1.getColor();
  short fontHeight = font1.getFontHeight();
  String fontName = font1.getFontName();
  boolean isItalic = font1.getItalic();
  boolean isStrikeout = font1.getStrikeout();
  short typeOffset = font1.getTypeOffset();
  byte underline = font1.getUnderline();

  Font font2 = wb2.findFont(isBold, color, fontHeight, fontName, isItalic, isStrikeout, typeOffset, underline);
  if (font2 == null) {
   font2 = wb2.createFont();
   font2.setBold(isBold);
   font2.setColor(color);
   font2.setFontHeight(fontHeight);
   font2.setFontName(fontName);
   font2.setItalic(isItalic);
   font2.setStrikeout(isStrikeout);
   font2.setTypeOffset(typeOffset);
   font2.setUnderline(underline);
  }

  return font2;
 }

 static void copyStyles(Cell cell1, Cell cell2) {
  CellStyle style1 = cell1.getCellStyle();
  Map<String, Object> properties = new HashMap<String, Object>();

  //CellUtil.DATA_FORMAT
  short dataFormat1 = style1.getDataFormat();
  if (BuiltinFormats.getBuiltinFormat(dataFormat1) == null) {
   String formatString1 = style1.getDataFormatString();
   DataFormat format2 = cell2.getSheet().getWorkbook().createDataFormat();
   dataFormat1 = format2.getFormat(formatString1);
  }
  properties.put(CellUtil.DATA_FORMAT, dataFormat1);

  //CellUtil.FILL_PATTERN  
  //CellUtil.FILL_FOREGROUND_COLOR 
  FillPatternType fillPattern = style1.getFillPattern();
  short fillForegroundColor = style1.getFillForegroundColor(); //gets only indexed colors, no custom HSSF or XSSF colors
  properties.put(CellUtil.FILL_PATTERN, fillPattern);
  properties.put(CellUtil.FILL_FOREGROUND_COLOR, fillForegroundColor);

  //CellUtil.FONT
  Font font1 = cell1.getSheet().getWorkbook().getFontAt(style1.getFontIndexAsInt());
  Font font2 = copyFont(font1, cell2.getSheet().getWorkbook());
  properties.put(CellUtil.FONT, font2.getIndexAsInt());

  //BORDERS
  BorderStyle borderStyle = null;
  short borderColor = -1;
  //CellUtil.BORDER_LEFT 
  //CellUtil.LEFT_BORDER_COLOR
  borderStyle = style1.getBorderLeft();
  properties.put(CellUtil.BORDER_LEFT, borderStyle);
  borderColor = style1.getLeftBorderColor();
  properties.put(CellUtil.LEFT_BORDER_COLOR, borderColor);
  //CellUtil.BORDER_RIGHT 
  //CellUtil.RIGHT_BORDER_COLOR
  borderStyle = style1.getBorderRight();
  properties.put(CellUtil.BORDER_RIGHT, borderStyle);
  borderColor = style1.getRightBorderColor();
  properties.put(CellUtil.RIGHT_BORDER_COLOR, borderColor);
  //CellUtil.BORDER_TOP 
  //CellUtil.TOP_BORDER_COLOR
  borderStyle = style1.getBorderTop();
  properties.put(CellUtil.BORDER_TOP, borderStyle);
  borderColor = style1.getTopBorderColor();
  properties.put(CellUtil.TOP_BORDER_COLOR, borderColor);
  //CellUtil.BORDER_BOTTOM 
  //CellUtil.BOTTOM_BORDER_COLOR
  borderStyle = style1.getBorderBottom();
  properties.put(CellUtil.BORDER_BOTTOM, borderStyle);
  borderColor = style1.getBottomBorderColor();
  properties.put(CellUtil.BOTTOM_BORDER_COLOR, borderColor);

  CellUtil.setCellStyleProperties(cell2, properties);
 }

 static void copyCells(Cell cell1, Cell cell2) {
  switch (cell1.getCellType()) {
   case STRING:
   /*
    //TODO: copy HSSFRichTextString to XSSFRichTextString 
    RichTextString rtString1 = cell1.getRichStringCellValue();
    cell2.setCellValue(rtString1); // this fails if cell2 is XSSF and rtString1 is HSSF
   */
    String string1 = cell1.getStringCellValue();
    cell2.setCellValue(string1);
   break;
   case NUMERIC:
    if (DateUtil.isCellDateFormatted(cell1)) {
     Date date1 = cell1.getDateCellValue();
     cell2.setCellValue(date1);
    } else {
     double cellValue1 = cell1.getNumericCellValue();
     cell2.setCellValue(cellValue1);
    }
   break;
   case FORMULA:
    String formula1 = cell1.getCellFormula();
    cell2.setCellFormula(formula1);
   break;

   //case : //TODO: further cell types

  }

  copyStyles(cell1, cell2);

 }

 public static void main(String[] args) throws Exception {
  Workbook wb1 = WorkbookFactory.create(new FileInputStream("Workbook.xls"));
  Workbook wb2 = new XSSFWorkbook();

  Sheet sheet1 = wb1.getSheetAt(0);
  Sheet sheet2 = wb2.createSheet();

  Set<Integer> columns = new HashSet<Integer>();
  Row row2 = null;
  Cell cell2 = null;
  for (Row row1 : sheet1) {
   row2 = sheet2.createRow(row1.getRowNum());
   for (Cell cell1 : row1) {
    columns.add(cell1.getColumnIndex());
    cell2 = row2.createCell(cell1.getColumnIndex());
    copyCells(cell1, cell2);
   }
  }

  wb1.close();

  for (Integer column : columns) {
   sheet2.autoSizeColumn(column);
  }

  FileOutputStream out = new FileOutputStream("Workbook.xlsx");
  wb2.write(out);
  out.close();
  wb2.close();
 }
}

If Workbook.xls looks like this:

enter image description here

then the resulting Workbook.xlsx looks like this:

enter image description here

Note: This is a working draft and needs to be completed. See TODO comments in the code. RichTextString cell values needs to be considered. Further cell types needs to be considered.

Method copyStyles only provides copying data format, fill pattern and fill foreground color (only for indexed colors), font and borders. Further cell style properties needs to be considered.

Axel Richter
  • 56,077
  • 6
  • 60
  • 87