You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 13:09:57