最近项目中需要生成word文件,于是找到了Apache的POI开源项目,当前版本是3.9;
当然除了POI还有其它的选择,比如java2word、jcob等,但我都没用过,好像只能在windows上运行,于是决定还是使用POI。
下载了最新版本研究半天,感觉POI和其它的Apache项目相比在文档方面要逊色很多,POI的文档极其不全,甚至连Java API docs里都没有什么描述信息,类和方法都光秃秃的,很多东西都要去猜,很是郁闷...
幸好网上有不少博客,磕磕绊绊也能大体了解如何使用。
问题描述
由于项目暂时只考虑word2007及以上版本,所以我主要关注了XWPF部分。
在向生成的word文档中插入图片时遇到了如下问题:
1、使用XWPFRun类的addPicture方法插入图片时,生成的docx文件无法打开,word报如下问题:
2、使用XWPFDocument类的方法addPictureData插入图片时虽然不报异常,word也能打开生成的文件,但图片却无法显示出来,如果将word文件用解压缩文件打开,也能看到里面包含的图片文件,但在word中无法正常显示。
问题原因
于是百度Google了一番,发现遇到此问题的人还真不少,但少有人找到正确的解决方法,而且这是POI自身的Bug,详细情况请看这里:
https://issues.apache.org/bugzilla/show_bug.cgi?id=49765
这是老外程序员向Apache提出的Bug,提出时间 2010年,奇怪的是现在都2013年了,POI也发展到3.9版本了,问题依然没有解决(至少知道3.7和3.8是没有解决,如果3.9解决了,那就是我哪地方没做对;但我把3.9版本官方提供的例子运行起来,问题依旧,想必3.9也是存在该问题),不知道是什么原因。
虽然POI没有解决,但在下面的回复中还是有人找到了原因并提供了解决方案。
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也支持),有空要试一下。