使用POI在Excel中添加外部图片

本篇的代码主要参考自poi讨论组:http://apache-poi.1045710.n5.nabble.com/Load-remote-image-inside-excel-sheet-td5709821.html

在通常情况下,我们在Excel中添加图片,只要是通过流的方式将图片写入excel特定的picture对象中。网上也有很多的例子,本文主要讲的是如何将一个外部的图片以链接的方式添加进excel中。这样,图片以外链的方式存储,在后台写excel文件时不需要处理图片存储的问题,在下载excel时也不用担心文件太大的问题了。

首先在excel添加外部图片,在添加图片时,只需要选择 以 链接的方式添加即可。这样,在excel时存储时并不需要存储实际图片的内容。这种方式也支持添加网络上的图片。分析这2种不同的添加方式,通过将xlsx文件进行解压(直接将文件修改为zip后缀,再解压即要)之后,在路径 \xl\drawings\_rels下面的文件drawing1.xml.rels可看到以下的不同内容:

<!-- 外部引用存储 -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="file:///C:\Users\Administrator\Desktop\图像%202.png" TargetMode="External"/></Relationships>
<!-- 内部引用存储 -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image1.png"/></Relationships>

同上可以看出,两者的区别就在于一个是引用一个../media的内部地址,实际上就是把整个图片写在内部压缩文件的一个位置上。而外部存储,则实际上直接写的是一个原始的地址。以协议名://地址的方式进行引用。
当然,不相同的地址肯定不只这一种,以上图片只是标识。虽然poi不支持添加外部图片,但通过poi自带的相关API,我们仍是可以添加外部图片的,使用的API,就是poi内部使用的工具openxml4j。

创建内部图片

//创建图片数据对象
int pictureIndex = wb.addPicture(byteArrayOut.toByteArray(), HSSFWorkbook.PICTURE_TYPE_JPEG);
//添加图片信息
patriarch.createPicture(anchor,pictureIndex);

以上步骤分为2步,第一步是创建一个图片数据对象,称之为PictureData对象。第二步是将此对象添加到相应的对象引用中。我们将第二段代码还原一下,如下所示:

//类XSSFDrawing
    public XSSFPicture createPicture(XSSFClientAnchor anchor, int pictureIndex)
    {
        PackageRelationship rel = addPictureReference(pictureIndex);//第一步

        long shapeId = newShapeId();
        CTTwoCellAnchor ctAnchor = createTwoCellAnchor(anchor);
        CTPicture ctShape = ctAnchor.addNewPic();
        ctShape.set(XSSFPicture.prototype());

        ctShape.getNvPicPr().getCNvPr().setId(shapeId);

        XSSFPicture shape = new XSSFPicture(this, ctShape);
        shape.anchor = anchor;
        shape.setPictureReference(rel);//第二步
        return shape;
    }

可以看出,第1步中是根据图片数据索引再次创建一个引用关系,这里就对应于我们在前面所说的表示不同引用地址的Relationship对象。原代码如下所示:

    protected PackageRelationship addPictureReference(int pictureIndex){
        XSSFWorkbook wb = (XSSFWorkbook)getParent().getParent();
        XSSFPictureData data = wb.getAllPictures().get(pictureIndex);
        PackagePartName ppName = data.getPackagePart().getPartName();
        PackageRelationship rel = getPackagePart().addRelationship(ppName, TargetMode.INTERNAL, XSSFRelation.IMAGES.getRelation());//此步骤是关键
        addRelation(rel.getId(),new XSSFPictureData(data.getPackagePart(), rel));
        return rel;
    }

上面的代码其实就是使用图片数据对象创建一个relation对象,此部分表示关系为Internal,即内部引用之前已经创建好的图片数据。

再回到第2步,即是对整个relationship在外部创建关系,即之前在 路径 \xl\drawings\_rels 的外层 \xl\drawing处的 drawing1.xml 文件。进入第二步方法代码,如下所示:

//类XSSFPicture
    protected void setPictureReference(PackageRelationship rel){
        ctPicture.getBlipFill().getBlip().setEmbed(rel.getId());
    }

如上所示,设置关系为embed方式,即以嵌入的方式使用内部的relation对象。

总结一下,即内部图片,需要处理2个东西,一是需要一个内部的图片数据对象,第二就是在创建关系时使用internal或者是embed关系。那么如果创建外部图片,则就直接使用external或者是linked方式不就OK了。

创建外部图片

因为原poi未提供,此处直接附相应的代码:

        PackageRelationship rel = patriarch.getPackagePart().addExternalRelationship(imageUrl, XSLFRelation.IMAGES.getRelation());//对应第一步中创建图片的realationship对象,因为不需要图片数据,因此这里直接创建外部对象。使用addExternal方法
//这部分直接copy自之前的xssfDrawing的createPicture(XSSFClientAnchor anchor, int pictureIndex) 代码
        long shapeId = newShapeId(drawing);
        CTTwoCellAnchor ctAnchor = createTwoCellAnchor(drawing, anchor);
        ctAnchor.setEditAs(STEditAs.TWO_CELL);
        CTPicture ctShape = ctAnchor.addNewPic();
        ctShape.set(XSSFPicture.prototype());
        ctShape.getNvPicPr().getCNvPr().setId(shapeId);
        XSSFPicture shape = new XSSFPicture(drawing, ctShape);
        shape.anchor = anchor;
//此部分不再使用shape.setReferenc,而是调用其内部实现,因为不再使用setEmbed方式,而是使用setLink,即使用linked方式
        shape.getCTPicture().getBlipFill().getBlip().setLink(rel.getId());
        return shape;

通过对上面的代码分析,即最终达到了我们创建外部图片的目的。
由于在poi中,很多api不是公共的(package或者是protected),因此部分api在调用时可能会报错。上面的代码部分使用反射方法调用。

总结

在上面的不同实现中,我们仅达到了保证生成的excel中可以访问外部图,并不能保证使用poi读取这个excel中,还能使用原来的代码读取其中的图片数据。这也是为什么在poi中没有提供插入外部图片的api,因为一旦提供了。之前的读取excel的代码就可能会出错。设想一下,使用poi获取到一个表示外部图片的picture对象,如何获取其byte[]数据。这就涉及到api兼容的问题了。正如开篇的贴子中提供,有方法可以生成excel,但这个方法不会提供在api中。……
还好在大部分的业务中,poi的使用是单向的,生成excel的业务场景不同同时读取excel,而读取excel的场景不会提供excel的生成。这样就避免了问题的产生。

转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/201412280001.html

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注