java使用poi,将xls文件转换为xlsx文件
最近產品有需求,需要在線瀏覽xls文件,猶豫前端的spreadJs只支持xlsx格式的瀏覽,所以讓我們服務端,將xls文件轉換為xlsx。
主要麻煩之處是圖片的拷貝,花費了一些時間,先將代碼貼在此處。
代碼環境:jdk 1.8、poi 4.1.2
poi的目前沒有原生的拷貝,所以需要自己一個個sheet、一行行、一個個單元格進行拷貝。第一步就是選擇需要拷貝的sheet,此處,遍歷所有的sheet,我這邊根據需求,只拷貝了,未隱藏的sheet。如果需要全部拷貝,可以去除判斷,再給新的sheet設置是否隱藏。
int sheetNum = hssfWorkbook.getNumberOfSheets();for (int sheetIndex = 0; sheetIndex < sheetNum; sheetIndex++) {// 只解析未隱藏的sheet // hssfSheet = hssfWorkbook.getSheetAt(sheetIndex); // xssfSheet = workbook.createSheet(hssfSheet.getSheetName()); // ExcelUtils.copySheets(hssfSheet, xssfSheet);if (SheetVisibility.VISIBLE == hssfWorkbook.getSheetVisibility(sheetIndex)) {hssfSheet = hssfWorkbook.getSheetAt(sheetIndex);xssfSheet = workbook.createSheet(hssfSheet.getSheetName());ExcelUtils.copySheets(hssfSheet, xssfSheet);}}第二步就是拷貝sheet。此處,需要遍歷行信息,進行行拷貝。在拷貝完所有信息之后,再拷貝圖片信息。
public static void copySheets(HSSFSheet source, XSSFSheet destination) {int maxColumnNum = 0;// 獲取全部的合并單元格List<CellRangeAddress> cellRangeAddressList = source.getMergedRegions();for (int i = source.getFirstRowNum(); i <= source.getLastRowNum(); i++) {HSSFRow srcRow = source.getRow(i);XSSFRow destRow = destination.createRow(i);if (srcRow != null) {// 拷貝行copyRow(destination, srcRow, destRow, cellRangeAddressList);if (srcRow.getLastCellNum() > maxColumnNum) {maxColumnNum = srcRow.getLastCellNum();}}}for (int i = 0; i <= maxColumnNum; i++) {destination.setColumnWidth(i, source.getColumnWidth(i));}// 拷貝圖片copyPicture(source, destination);}以下是拷貝行信息,和單元格信息的代碼。
此處代碼不做詳細解釋,基本就是拷貝合并單元格、拷貝文字樣式等。
注:從hssf 轉換到xssf 許多樣式無法直接利用方法拷貝,需要逐一設置,可能會有遺漏。
單元格文本,此處也是統一復制,所以每個單元格的文本樣式是相同的,如果要求比較高,可以使用XSSFRichTextString 進行每個字符的字體大小設置。
// 拷貝行public static void copyRow(XSSFSheet destSheet, HSSFRow srcRow, XSSFRow destRow,List<CellRangeAddress> cellRangeAddressList) {// 拷貝行高destRow.setHeight(srcRow.getHeight());for (int j = srcRow.getFirstCellNum(); j <= srcRow.getLastCellNum(); j++) {HSSFCell oldCell = srcRow.getCell(j);XSSFCell newCell = destRow.getCell(j);if (oldCell != null) {if (newCell == null) {newCell = destRow.createCell(j);}// 拷貝單元格copyCell(oldCell, newCell, destSheet);// 獲取原先的合并單元格CellRangeAddress mergedRegion = getMergedRegion(cellRangeAddressList, srcRow.getRowNum(),(short) oldCell.getColumnIndex());if (mergedRegion != null) {// 參照創建合并單元格CellRangeAddress newMergedRegion = new CellRangeAddress(mergedRegion.getFirstRow(),mergedRegion.getLastRow(), mergedRegion.getFirstColumn(), mergedRegion.getLastColumn());destSheet.addMergedRegion(newMergedRegion);}}}}// 拷貝單元格public static void copyCell(HSSFCell oldCell, XSSFCell newCell, XSSFSheet destSheet) {HSSFCellStyle sourceCellStyle = oldCell.getCellStyle();XSSFCellStyle targetCellStyle = destSheet.getWorkbook().createCellStyle();if (sourceCellStyle == null) {sourceCellStyle = oldCell.getSheet().getWorkbook().createCellStyle();}targetCellStyle.setFillForegroundColor(sourceCellStyle.getFillForegroundColor());// 設置對齊方式targetCellStyle.setAlignment(sourceCellStyle.getAlignment());targetCellStyle.setVerticalAlignment(sourceCellStyle.getVerticalAlignment());// 設置字體XSSFFont xssfFont = destSheet.getWorkbook().createFont();HSSFFont hssfFont = sourceCellStyle.getFont(oldCell.getSheet().getWorkbook());copyFont(xssfFont, hssfFont);targetCellStyle.setFont(xssfFont);// 文本換行targetCellStyle.setWrapText(sourceCellStyle.getWrapText());targetCellStyle.setBorderBottom(sourceCellStyle.getBorderBottom());targetCellStyle.setBorderLeft(sourceCellStyle.getBorderLeft());targetCellStyle.setBorderRight(sourceCellStyle.getBorderRight());targetCellStyle.setBorderTop(sourceCellStyle.getBorderTop());targetCellStyle.setBottomBorderColor(sourceCellStyle.getBottomBorderColor());targetCellStyle.setDataFormat(sourceCellStyle.getDataFormat());targetCellStyle.setFillBackgroundColor(sourceCellStyle.getFillBackgroundColor());targetCellStyle.setFillPattern(sourceCellStyle.getFillPattern());targetCellStyle.setHidden(sourceCellStyle.getHidden());targetCellStyle.setIndention(sourceCellStyle.getIndention());targetCellStyle.setLeftBorderColor(sourceCellStyle.getLeftBorderColor());targetCellStyle.setLocked(sourceCellStyle.getLocked());targetCellStyle.setQuotePrefixed(sourceCellStyle.getQuotePrefixed());targetCellStyle.setReadingOrder(ReadingOrder.forLong(sourceCellStyle.getReadingOrder()));targetCellStyle.setRightBorderColor(sourceCellStyle.getRightBorderColor());targetCellStyle.setRotation(sourceCellStyle.getRotation());newCell.setCellStyle(targetCellStyle);switch (oldCell.getCellType()) {case STRING:newCell.setCellValue(oldCell.getStringCellValue());break;case NUMERIC:newCell.setCellValue(oldCell.getNumericCellValue());break;case BLANK:newCell.setCellType(CellType.BLANK);break;case BOOLEAN:newCell.setCellValue(oldCell.getBooleanCellValue());break;case ERROR:newCell.setCellErrorValue(oldCell.getErrorCellValue());break;case FORMULA:newCell.setCellFormula(oldCell.getCellFormula());break;default:break;}}// 拷貝字體設置public static void copyFont(XSSFFont xssfFont, HSSFFont hssfFont) {xssfFont.setFontName(hssfFont.getFontName());xssfFont.setBold(hssfFont.getBold());xssfFont.setFontHeight(hssfFont.getFontHeight());xssfFont.setCharSet(hssfFont.getCharSet());xssfFont.setColor(hssfFont.getColor());xssfFont.setItalic(hssfFont.getItalic());xssfFont.setUnderline(hssfFont.getUnderline());xssfFont.setTypeOffset(hssfFont.getTypeOffset());xssfFont.setStrikeout(hssfFont.getStrikeout());}// 根據行列獲取合并單元格public static CellRangeAddress getMergedRegion(List<CellRangeAddress> cellRangeAddressList, int rowNum, short cellNum) {for (int i = 0; i < cellRangeAddressList.size(); i++) {CellRangeAddress merged = cellRangeAddressList.get(i);if (merged.isInRange(rowNum, cellNum)) {// 已經獲取過不再獲取cellRangeAddressList.remove(i);return merged;}}return null;}最后就是讓我最煩惱的地方,圖片的拷貝。
最艱難的地方就是,hssf和xssf兩種 對圖片偏移的解析。
poi官方文檔,兩者的注釋是一樣的,我也被誤導了。最后翻看源代碼發現
hssf的偏移是根據系統按比例來的。單元格無論多寬,總寬度都為1024,總高度都為256。
xssf的偏移則是以emu為單位的。
所以hssf轉xssf 正確的公式應該如下:
圖片偏移x坐標 = hssf單元格寬度 / 1024 * hssf圖片偏移比例 * 每個像素對應的emu
圖片偏移y坐標 = hssf單元格高度?/ 256?* hssf圖片偏移比例 * 每個Point對應的emu
注:此處獲取單元格寬度還是有問題,默認的獲取方法有。getColumnWidthInPixels 和?getColumnWidth。getColumnWidthInPixels 是單元格像素寬度,getColumnWidth也是一種相對寬度。但是實際測試下來會有問題,
此處 cw 與 def 不相等,最后 獲取的像素寬度是?getDefaultColumnWidth * 256 /?PX_MODIFIED,
但實際結果應該是?getDefaultColumnWidth * 256 /?PX_DEFAULT?
原因未查明,此處我在代碼中放棄了getColumnWidthInPixels 改用了??getDefaultColumnWidth * 256 /?PX_DEFAULT
@Override public float getColumnWidthInPixels(int column){int cw = getColumnWidth(column);int def = getDefaultColumnWidth()*256;float px = (cw == def ? PX_DEFAULT : PX_MODIFIED);return cw/px; }最后結果代碼如下:
// 拷貝圖片public static void copyPicture(HSSFSheet source, XSSFSheet destination) {// 獲取sheet中的圖片信息List<Map<String, Object>> mapList = getPicturesFromHSSFSheet(source);XSSFDrawing drawing = destination.createDrawingPatriarch();for (Map<String, Object> pictureMap: mapList) {HSSFClientAnchor hssfClientAnchor = (HSSFClientAnchor) pictureMap.get("pictureAnchor");HSSFRow startRow = source.getRow(hssfClientAnchor.getRow1());float startRowHeight = startRow == null ? source.getDefaultRowHeightInPoints() : startRow.getHeightInPoints();HSSFRow endRow = source.getRow(hssfClientAnchor.getRow1());float endRowHeight = endRow == null ? source.getDefaultRowHeightInPoints() : endRow.getHeightInPoints();// hssf的單元格,每個單元格無論寬高,都被分為 寬 1024個單位 高 256個單位。// 32.00f 為默認的單元格單位寬度 單元格寬度 / 默認寬度 為像素寬度XSSFClientAnchor xssfClientAnchor = drawing.createAnchor((int) (source.getColumnWidth(hssfClientAnchor.getCol1()) / 32.00f/ 1024 * hssfClientAnchor.getDx1() * Units.EMU_PER_PIXEL),(int) (startRowHeight / 256 * hssfClientAnchor.getDy1() * Units.EMU_PER_POINT),(int) (source.getColumnWidth(hssfClientAnchor.getCol2()) / 32.00f/ 1024 * hssfClientAnchor.getDx2() * Units.EMU_PER_PIXEL),(int) (endRowHeight / 256 * hssfClientAnchor.getDy2() * Units.EMU_PER_POINT),hssfClientAnchor.getCol1(),hssfClientAnchor.getRow1(),hssfClientAnchor.getCol2(),hssfClientAnchor.getRow2());xssfClientAnchor.setAnchorType(hssfClientAnchor.getAnchorType());drawing.createPicture(xssfClientAnchor,destination.getWorkbook().addPicture((byte[])pictureMap.get("pictureByteArray"),Integer.parseInt(pictureMap.get("pictureType").toString())));System.out.println("imageInsert");}}/*** 獲取圖片和位置 (xls)*/public static List<Map<String, Object>> getPicturesFromHSSFSheet (HSSFSheet sheet) {List<Map<String, Object>> mapList = new ArrayList<>();List<HSSFShape> list = sheet.getDrawingPatriarch().getChildren();for (HSSFShape shape : list) {if (shape instanceof HSSFPicture) {Map<String, Object> map = new HashMap<>();HSSFPicture picture = (HSSFPicture) shape;HSSFClientAnchor cAnchor = picture.getClientAnchor();HSSFPictureData pdata = picture.getPictureData();map.put("pictureAnchor", cAnchor);map.put("pictureByteArray", pdata.getData());map.put("pictureType", pdata.getPictureType());map.put("pictureSize", picture.getImageDimension());mapList.add(map);}}return mapList;}}總結
以上是生活随笔為你收集整理的java使用poi,将xls文件转换为xlsx文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 可验证随机函数(Verifiable R
- 下一篇: mysql 随机函数