一、场景需求:
有个项目需要将某个题库里面的题目导出word2003,之前框架里面没有导出word2003的工具类。倒是见过导出txt,excel的,但是word的没有。
二、技术选型
网上找了一通,导出word的有几个技术方案,不过对于word2003版导出的例子比较少。
程序导出word思路(2003版的)
1、利用freemarker来做,xml定义doc的模版,程序动态获取数据,塞值。这个没去试了,时间不够。
2、利用itext来做,能导出来,据说格式是rtf,能用word打开。不过poi导入这个生成的word不支持,poi导入底层源码有对rtf格式做校验,会抛异常。看过源码,但没有试过用itext来做。
/**
* Takens an InputStream, verifies that it's not RTF, builds a
* POIFSFileSystem from it, and returns that.
*/
public static POIFSFileSystem verifyAndBuildPOIFS(InputStream istream) throws IOException {
// Open a PushbackInputStream, so we can peek at the first few bytes
PushbackInputStream pis = new PushbackInputStream(istream,6);
byte[] first6 = new byte[6];
pis.read(first6);
// Does it start with {\rtf ? If so, it's really RTF
if(first6[0] == '{' && first6[1] == '\\' && first6[2] == 'r'
&& first6[3] == 't' && first6[4] == 'f') {
throw new IllegalArgumentException("The document is really a RTF file");
}
// OK, so it's not RTF
// Open a POIFSFileSystem on the (pushed back) stream
pis.unread(first6);
return new POIFSFileSystem(pis);
}
3、利用poi来做,poi操作word2003,api和demo极少,如果能用07版word的,建议用07,网上07版的例子比较多,也能用07版api自己操作创建段落,页码,页面。本来项目要求03版的。
由于项目要求支持导出word,也能将导出的word再次导入到题库里面,因此选用了poi作为03版word导出。(项目导入word也是用poi)
三、遇到的问题
1、poi本身对word的支持度就不高,官网的poi导出word的demo少得可怜。Apache官网自行去搜api吧。
2、用poi导出word,大多数用模板来做,用个标记语言在程序中和对应的word版本中,程序里面替换。这篇博客有写这个模板替换的
3、项目要求的是动态的数据,题目里面有不同题型,还有子题,用模板的方式显然不行。
为此,想对着api,研究一番,不过项目时间紧,赶着上线。和老大沟通过,老大也知道poi对word的支持比较差,demo和api都比较少。不过他觉得既然能导入,应该也是能导出的。
只能自己硬着头皮搞——老大也没有搞过这个。一开始想着程序拼装好动态数据,拼成一个html页面,用poi将html转成word。是成功了。但是生成的word通过poi再次导入,报数组越界之类的。明明用poi导出,再poi导入还是有问题。估计是html的word版丢失了一些真正word需要的标记符号之类。
卡在这里了。
继续试试api,忽然想起看到api有个range.insertBefore(String text)。这个可能有机会突破。正是这个api,成为了解决问题的关键。
最后解决办法:
poi生成word,先读取一个空白模版doc进内存,用该doc对象的range对象上面提到的方法,插入内容,然后将该range用输出流写入到另外一个已存在的空白word文档文件。ok,能导出word,这个导出的word也能通过poi导入。
通过空白文档的word对象,设置动态数据到该word对象(这里不会将动态数据写入到空白文档的文件当中,仅仅是在内存中的),然后再将该有数据的word对象持久化到另外一个word文件里面。这样导出的word也能通过poi导进程序当中。
String filePath = "xxx要导出的word.doc";
String wordContent = "动态数据";
try {
File doc = new File(filePath) ;
if(null == doc || !doc.exists()){
doc.createNewFile() ;
}
String realPathFile = "空白的WORD(填充数据所用).doc";//这个空白word需要手动自己创建一个word
FileInputStream in = new FileInputStream(realPathFile);
HWPFDocument hwpfDocument = new HWPFDocument(in);
Range range = hwpfDocument.getRange();//获取整个空白的word的文档对象
range.insertBefore(wordContent);//插入数据
ByteArrayOutputStream ostream = null;
ostream = new ByteArrayOutputStream();
hwpfDocument.write(ostream);
FileOutputStream fos = new FileOutputStream(doc);
fos.write(ostream.toByteArray());} catch (Exception e) {
e.printStackTrace();
}
不过,生成的word样式还是有些问题。每一行的开头有时候会对不齐,隔了大概一个空白字符。这里无法设置样式。不知道有木有朋友也是这样做。欢迎交流~
动态数据当中是用\r\n和\r作回车和换行的。这里也有一个坑,windos和linux的回车、换行是不相同的。
在此,参考了一些资源,谢谢技术分享:
http://blog.csdn.net/lmb55/article/details/64519658
幸好,最终还是做出来了。用了两天不到的时间。