本篇的代码主要参考自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