Apache POI拆分Excel复制图片显示空白占位符问题求助
Apache POI拆分Excel时图片迁移失败排查
我正在使用Apache POI API将包含多个工作表的Excel文件拆分为多个单工作表Excel文件,但迁移旧工作表中的图片后,打开新文件仅显示空白图片占位符(如下图)。将原文件和新文件改后缀为.zip解压后对比,发现新文件未创建media文件夹和drawings/_rel文件夹。

以下是我用于复制图片的代码:
private void copyImage(XSSFSheet srcSheet_, XSSFSheet dstSheet_) { XSSFWorkbook srcWorkbook_ = srcSheet_.getWorkbook(); XSSFWorkbook dstWorkbook_ = dstSheet_.getWorkbook(); List<POIXMLDocumentPart> pdp = srcWorkbook_.getRelations(); List<POIXMLDocumentPart.RelationPart> rels = srcSheet_.getRelationParts(); XSSFDrawing dg = null; for (POIXMLDocumentPart.RelationPart rp : rels) { POIXMLDocumentPart r = rp.getDocumentPart(); if (r instanceof XSSFDrawing) { dg = (XSSFDrawing) r; continue; } addRelation(rp, dstSheet_); } try { for (PackageRelationship pr : srcSheet_.getPackagePart().getRelationships()) { if (pr.getTargetMode() == TargetMode.EXTERNAL) { dstSheet_.getPackagePart().addExternalRelationship(pr.getTargetURI().toASCIIString(), pr.getRelationshipType(), pr.getId()); } } } catch (org.apache.poi.openxml4j.exceptions.InvalidFormatException e) { throw new POIXMLException("Failed to clone sheet", e); } CTWorksheet ct = dstSheet_.getCTWorksheet(); if (ct.isSetLegacyDrawing()) { log.atWarn().log("Cloning sheets with comments is not yet supported."); ct.unsetLegacyDrawing(); } if (ct.isSetPageSetup()) { log.atWarn().log("Cloning sheets with page setup is not yet supported."); ct.unsetPageSetup(); } dstSheet_.setSelected(false); if (dg != null) { if (ct.isSetDrawing()) { ct.unsetDrawing(); } XSSFDrawing clonedDg = dstSheet_.createDrawingPatriarch(); // copy drawing contents clonedDg.getCTDrawing().set(dg.getCTDrawing().copy()); XSSFDrawing drawingPatriarch = srcSheet_.getDrawingPatriarch(); if (drawingPatriarch != null) { List<POIXMLDocumentPart.RelationPart> srcRels = drawingPatriarch.getRelationParts(); for (POIXMLDocumentPart.RelationPart rp : srcRels) { POIXMLDocumentPart r = rp.getDocumentPart(); int chartNumber = r.getPackagePart().getPackage().getPartsByContentType(XSSFRelation.CHART.getContentType()).size() + 1; int imageNumber = r.getPackagePart().getPackage().getPartsByContentType(XSSFRelation.IMAGE_PNG.getContentType()).size() + 1; if (r instanceof XSSFChart) { POIXMLDocumentPart.RelationPart chartPart = clonedDg.createRelationship(XSSFRelation.CHART, dstWorkbook_.getXssfFactory(), chartNumber, false); XSSFChart chart = chartPart.getDocumentPart(); chart.importContent((XSSFChart) r); chart.replaceReferences(dstSheet_); } else if (r instanceof XSSFPictureData){ XSSFPictureData img = r.createRelationship(XSSFRelation.IMAGE_PNG, dstWorkbook_.getXssfFactory(), imageNumber,true).getDocumentPart(); try (OutputStream out = img.getPackagePart().getOutputStream()){ out.write(((XSSFPictureData) r).getData()); }catch (IOException e){ throw new POIXMLException(e); } } else { addRelation(rp, clonedDg); } } } } }
问题原因与修正方案
1. 图片类型硬编码限制
当前代码仅处理XSSFRelation.IMAGE_PNG类型,但Excel中的图片可能是JPG、GIF等其他格式,硬编码PNG会导致非PNG图片无法被正确复制,也不会生成对应的media文件夹。
修正:
获取原图片的实际关系类型,适配所有图片格式:
else if (r instanceof XSSFPictureData){ XSSFPictureData srcPic = (XSSFPictureData) r; // 获取原图片的关系类型,兼容所有格式 POIXMLRelation relation = XSSFRelation.getInstance(srcPic.getPackagePart().getContentType()); int imageNumber = dstWorkbook_.getPackage().getPartsByContentType(relation.getContentType()).size() + 1; XSSFPictureData img = clonedDg.createRelationship(relation, dstWorkbook_.getXssfFactory(), imageNumber, true).getDocumentPart(); try (OutputStream out = img.getPackagePart().getOutputStream()){ out.write(srcPic.getData()); }catch (IOException e){ throw new POIXMLException(e); } }
2. CTDrawing直接拷贝导致关系ID不匹配
直接复制CTDrawing的整个结构会导致原有的资源关系ID和新文件中的ID不匹配,Excel无法找到对应的图片资源,也不会生成drawings/_rel文件夹。
修正:
逐个复制图片形状(XSSFPicture),让Apache POI自动处理关系和ID映射:
// 替换原有的 clonedDg.getCTDrawing().set(dg.getCTDrawing().copy()); 代码段 if (dg != null) { if (ct.isSetDrawing()) { ct.unsetDrawing(); } XSSFDrawing clonedDg = dstSheet_.createDrawingPatriarch(); // 遍历原绘图中的所有形状,逐个复制图片 for (XSSFShape shape : dg.getShapes()) { if (shape instanceof XSSFPicture) { XSSFPicture srcPic = (XSSFPicture) shape; XSSFPictureData srcPicData = srcPic.getPictureData(); // 获取原图片的类型,添加到新工作簿 POIXMLRelation relation = XSSFRelation.getInstance(srcPicData.getPackagePart().getContentType()); int imgIdx = dstWorkbook_.addPicture(srcPicData.getData(), relation.getPictureType()); // 创建新图片并复制位置、样式 XSSFPicture newPic = clonedDg.createPicture(srcPic.getClientAnchor(), imgIdx); newPic.getCTPicture().getSpPr().set(srcPic.getCTPicture().getSpPr().copy()); newPic.getCTPicture().getBlipFill().set(srcPic.getCTPicture().getBlipFill().copy()); } } // 保留原有图表处理逻辑(如果需要) // ... 原有图表复制代码 ... }
3. addRelation方法需正确处理资源复制
如果自定义的addRelation方法未正确复制包关系,会导致依赖资源无法关联到新工作表。确保该方法正确复制关系的目标路径和内容:
private void addRelation(POIXMLDocumentPart.RelationPart rp, POIXMLDocumentPart dstPart) { POIXMLDocumentPart srcDocPart = rp.getDocumentPart(); POIXMLRelation relation = rp.getRelation(); // 创建新关系并复制资源内容 POIXMLDocumentPart.RelationPart newRp = dstPart.createRelationship(relation, dstPart.getPackage().getXSSFFactory(), -1, false); POIXMLDocumentPart newDocPart = newRp.getDocumentPart(); try { // 复制原资源的二进制内容 newDocPart.getPackagePart().getOutputStream().write(srcDocPart.getPackagePart().getInputStream().readAllBytes()); } catch (IOException e) { throw new POIXMLException("Failed to copy relation", e); } }
4. 验证修复效果
修正后重新运行代码,解压新生成的Excel文件,应能看到media文件夹(存放图片文件)和drawings/_rel文件夹(存放绘图与图片的关系映射),图片也能正常显示。
内容的提问来源于stack exchange,提问作者KhanhND
相关产品推荐
相关产品推荐

