POI向word2007文件插入图片时的Bug问题

时间:2022-12-09 10:49:04

最近项目中需要生成word文件,于是找到了Apache的POI开源项目,当前版本是3.9;

当然除了POI还有其它的选择,比如java2word、jcob等,但我都没用过,好像只能在windows上运行,于是决定还是使用POI。

下载了最新版本研究半天,感觉POI和其它的Apache项目相比在文档方面要逊色很多,POI的文档极其不全,甚至连Java API docs里都没有什么描述信息,类和方法都光秃秃的,很多东西都要去猜,很是郁闷...

幸好网上有不少博客,磕磕绊绊也能大体了解如何使用。


问题描述


由于项目暂时只考虑word2007及以上版本,所以我主要关注了XWPF部分。

在向生成的word文档中插入图片时遇到了如下问题:

1、使用XWPFRun类的addPicture方法插入图片时,生成的docx文件无法打开,word报如下问题:

POI向word2007文件插入图片时的Bug问题

2、使用XWPFDocument类的方法addPictureData插入图片时虽然不报异常,word也能打开生成的文件,但图片却无法显示出来,如果将word文件用解压缩文件打开,也能看到里面包含的图片文件,但在word中无法正常显示。


问题原因


于是百度Google了一番,发现遇到此问题的人还真不少,但少有人找到正确的解决方法,而且这是POI自身的Bug,详细情况请看这里:

https://issues.apache.org/bugzilla/show_bug.cgi?id=49765

POI向word2007文件插入图片时的Bug问题

这是老外程序员向Apache提出的Bug,提出时间 2010年,奇怪的是现在都2013年了,POI也发展到3.9版本了,问题依然没有解决(至少知道3.7和3.8是没有解决,如果3.9解决了,那就是我哪地方没做对;但我把3.9版本官方提供的例子运行起来,问题依旧,想必3.9也是存在该问题),不知道是什么原因。

虽然POI没有解决,但在下面的回复中还是有人找到了原因并提供了解决方案。

在回复的第14楼,有人这样回复

POI向word2007文件插入图片时的Bug问题

https://issues.apache.org/bugzilla/show_bug.cgi?id=49765#c14

本人英文不够好,勉强能看懂一点儿。就是说在\word\document.xml文件中上面图片中的那一大串XML内容没有被正确地生成,而图片本身被添加到正确的位置\word\media\xxxx.xxx,而且引用关系也正确添加。

word2007以后文件的默认存储格式不再是二进制文件格式,而是Microsoft Office Word XML格式(Word XML格式)。这种格式基于开放打包约定(Open Packaging Conventions),Microsoft Office 97到Microsoft Office 2003中使用的二进制文件格式仍然可以作为一种保存格式来使用,但是它不是保存新文档时的默认文档。

那么,上面的问题应该就是在插入图片时部分XML没有正确生成(\word\document.xml中,见上述图片)。

而且这位高手在第15楼也给出了变通方案:就是自定义一个XWPFDocument来完成缺陷XML内容的添加。

https://issues.apache.org/bugzilla/show_bug.cgi?id=49765#c15


解决方案


自定义XWPFDocument代码如下:

package com.zyh.sample.poi;

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlToken;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;

import java.io.IOException;
import java.io.InputStream;

public class CustomXWPFDocument extends XWPFDocument {
public CustomXWPFDocument() {
super();
}

public CustomXWPFDocument(OPCPackage opcPackage) throws IOException {
super(opcPackage);
}

public CustomXWPFDocument(InputStream in) throws IOException {
super(in);
}

public void createPicture(String blipId,int id, int width, int height) {
final int EMU = 9525;
width *= EMU;
height *= EMU;
//String blipId = getAllPictures().get(id).getPackageRelationship().getId();


CTInline inline = createParagraph().createRun().getCTR().addNewDrawing().addNewInline();

String picXml = "" +
"<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">" +
" <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +
" <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +
" <pic:nvPicPr>" +
" <pic:cNvPr id=\"" + id + "\" name=\"Generated\"/>" +
" <pic:cNvPicPr/>" +
" </pic:nvPicPr>" +
" <pic:blipFill>" +
" <a:blip r:embed=\"" + blipId + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>" +
" <a:stretch>" +
" <a:fillRect/>" +
" </a:stretch>" +
" </pic:blipFill>" +
" <pic:spPr>" +
" <a:xfrm>" +
" <a:off x=\"0\" y=\"0\"/>" +
" <a:ext cx=\"" + width + "\" cy=\"" + height + "\"/>" +
" </a:xfrm>" +
" <a:prstGeom prst=\"rect\">" +
" <a:avLst/>" +
" </a:prstGeom>" +
" </pic:spPr>" +
" </pic:pic>" +
" </a:graphicData>" +
"</a:graphic>";

//CTGraphicalObjectData graphicData = inline.addNewGraphic().addNewGraphicData();
XmlToken xmlToken = null;
try {
xmlToken = XmlToken.Factory.parse(picXml);
} catch(XmlException xe) {
xe.printStackTrace();
}
inline.set(xmlToken);
//graphicData.set(xmlToken);

inline.setDistT(0);
inline.setDistB(0);
inline.setDistL(0);
inline.setDistR(0);

CTPositiveSize2D extent = inline.addNewExtent();
extent.setCx(width);
extent.setCy(height);

CTNonVisualDrawingProps docPr = inline.addNewDocPr();
docPr.setId(id);
docPr.setName("Picture " + id);
docPr.setDescr("Generated");
}
}

使用也很简单,贴一个使用的小例子:

public static void main(String[] args) {
CustomXWPFDocument document = new CustomXWPFDocument();
try {
String picId = document.addPictureData(new FileInputStream("E:/20130325133325.png"), XWPFDocument.PICTURE_TYPE_PNG);
document.createPicture(picId, document.getNextPicNameNumber(XWPFDocument.PICTURE_TYPE_PNG), 200, 150);

FileOutputStream fos = new FileOutputStream(new File("D:\\updatedTemplate.docx"));
document.write(fos);
fos.close();
} catch (InvalidFormatException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

注:

1、相比较而言,国外的技术社区更活跃,还是应该多用Google(可惜有时无法访问,天朝!!!)。

2、在*上相关帖子中见到docx4j项目,主要是针对word2007及以上(xslx和pptx也支持),有空要试一下。