使用 Apache POI 在 Excel 个工作簿之间复制单元格

copy cells between Excel workbooks with Apache POI

我正在尝试使用最新版本的 Apache POI (4.1.2) 将单元格从一个工作簿复制到另一个工作簿。

如果两个工作簿都是 .xlsx 文件,则一切正常。但是如果源工作簿是一个(旧的).xls 文件而目标工作簿是一个.xlsx 文件,则以下代码将失败

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

抛出的异常是:

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

如果文件(或Workbook对象)的类型不同时我们不能使用cloneStyleFrom,我们如何将HSSFCellStyle对象转换为XSSFCellStyle?

您的问题 "How can we convert a HSSFCellStyle object to a XSSFCellStyle?" 的答案是:我们不能使用 apache poi 4.1.2 来做到这一点。正如 CellStyle.cloneStyleFrom 中明确指出的那样,这根本不受支持:"However, both of the CellStyles will need to be of the same type (HSSFCellStyle or XSSFCellStyle)."

另一个问题是:我们是否应该 将一种单元格样式转换为另一种?或者 CellStyle.cloneStyleFrom 有哪些用例?在我看来有none。唯一单元格 formats/cell 样式的数量有 Excel 限制。请参阅 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 以将这些样式属性设置到其他有问题的单元格。此方法尝试查找与单元格当前样式以及 properties 中的样式属性相匹配的现有 CellStyle。仅当工作簿不包含匹配样式时才会创建新样式。

由于你的问题标题是 "Copy cells between Excel workbooks with Apache POI",我已经创建了一份工作草案,说明我将如何做到这一点。

以下代码首先获取一个已存在的 Workbook.xls 作为 HSSFWorkbook wb1 并创建一个新的 XSSFWorkbook wb2。然后它遍历 wb1 的第一个 sheet 的所有单元格,并尝试将这些单元格复制到 wb2 的第一个 sheet 中。为此,有一种使用 copyStyles(Cell cell1, Cell cell2) 的方法 copyCells(Cell cell1, Cell cell2)。后者从 cell1 获取的源样式 style1 获取样式属性,然后使用 CellUtil.setCellStyleProperties 将这些样式属性设置为 cell2。使用 copyFont(Font font1, Workbook wb2) 复制字体。仅当该工作簿中尚不存在此类字体时,才会尝试在 wb2 中创建新字体。这是必要的,因为 Excel.

中每个工作簿的唯一字体类型也有限制

工作示例:

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();
 }
}

如果 Workbook.xls 看起来像这样:

那么结果 Workbook.xlsx 看起来像这样:

注意:这是工作草案,需要完成。请参阅代码中的 TODO 注释。 RichTextString 需要考虑单元格值。需要考虑更多的细胞类型。

方法copyStyles只提供了复制数据格式、填充图案和填充前景颜色(只针对索引颜色)、字体和边框。需要考虑更多的单元格样式属性。