e.printStackTrace();
tempDoc.deleteOnExit();
三、调试记录
1.关于创建临时temp.doc的尝试
目的:在程序开始时创建一个temp.doc,程序结束后删除。
尝试1:用File-createNewFile创建出文件,然后用POIFSFileSystem构造函数打开这个文件, 然后用HWPFDocument关联到这个POIFSFileSystem类实例。
结果:org.apache.poi.EmptyFileException: The supplied file was empty (zero bytes long)。创建出的文件是0字节空文件,不能被POIFSFileSystem打开。
尝试2:用POIFSFileSystem.create(file)静态函数创建POIFSFileSystem实例,然后用 HWPFDocument关联。
结果:java.io.FileNotFoundException: no such entry: “WordDocument”, had: []。文件 是创建成功了,也用POIFSFileSystem成功载入,但是HWPFDocument无法接收这个参数。
结论:目前只能用Office软件创建的word文档才行。也就是说暂时还没找到直接在程序中新建 一个word文档的方法,只能先创建好temp.doc,然后在程序中读取。
2.无法写入temp.doc
描述:用doc.getRange()方法获取文件的整个范围,然后用range.insertAfter(String)方法 插入数据。编译运行没有任何异常,但是打开文件发现还是原样。
尝试:插入数据后用range.text()获取当前range的所有文本并显示在控制台上,发现数据的 确是成功插入到了range中,但是temp.doc依然没有任何变化。
猜测:可能是文件读取到HWPFDocument的方式不对,只读不可写入。
也有可能是range中的内容并不会改变.doc的内容,必须doc.write(*)写入到另一个文件中才 行。
尝试:通过各种方式(inputstream,poifsfilesystem,(poifs,readonly))载入temp.doc,结 果都是一样。于是开始尝试第二种猜测。
3.加入doc.write(*)方法后,运行报错,找不到需要的类文件(编译正常)。
详情:只加了这一句话,这句话报错:doc.write(out):java.lang.NoClassDefFoundError错 误
分析:NoClassDefFoundError发生在编译时对应的类可用,而运行时在Java的classpath路径 中,对应的类不可用导致的错误。
解决:要注意看报错的提示:
Exception in thread “main” java.lang.NoClassDefFoundError: org/apache/commons/collections4/bidimap/TreeBidiMap……
可以看出,是org.apache.commons.collections4包找不到导致的。导入这个包即可。
收获:使用外部jar包时,并不是只把所有代码里用到的类所在的jar包导入就万事大吉了,经 常是代码中用到的类里需要用到其他包中的类。如果在运行时报错,要注意看报错提示,根据 提示导入相关的包。
就这样一个简答的小bug卡了我半天,以后代码出错时不要只看错误类型,一定要细看报错的 描述。
4.无法写入目标文件
详情:续2,通过doc.write(out)方法将数据写到字节输出流,目标文件毫无变化。
尝试:用doc.write方法将数据写到字节数组,看看数据是否真的被输出了(如果是,就说明 是数据写入目标文件的过程中出了问题,而不是doc.write输出的问题)
结果:在输出的内容中找到了想要输出的数据。由此说明,前面的一切都没问题了,问题出在 把数据写入word文档上。
尝试:将目标文件删除,让程序创建出一个。结果,写入成功。那么问题来了:
5.目标文件无法续写
详情:由程序自己创建出的word文档可以写入,但已存在的word文档无法续写。即使是程序自 己创建出的word文档,也只能写入一次,无法续写。
分析:Range输出的数据是带有word文档的创建信息和格式数据的,这些内容对于已存在的 word文档不适用。
现在的情况是:用于创建HWPFDocument对象的temp.doc必须手动创建;目标文件必须由代码生 成,且生成后只能用代码写入一次。
将数据写入指定word文档的流程是:用getRange()方法获得临时文件数据(其实是为了获取 word文档的创建信息、格式数据),然后将源文件数据写入range,最后将range写入目标文件 的字节输出流。
既然如此,为何不直接将临时文件的来源设为目标文件呢?这样getRange所获取的range就能 同时包含目标文件中的原有文本数据,再在其后添加源文件中的内容,然后将整个range写入 由代码新创建的目标文件,不就是另一种意义上的续写吗?这样既避免了手动创建temp.doc, 又能实现续写,还能让避免产生垃圾文件(无意义的temp.doc)
解决:考虑到输入输出的冲突问题,先将目标文件重命名为temp.doc,然后由程序新创建出一 个空的目标文件。如果需要续写,就直接用getRange方法获取原来的所有数据;如果不需要续 写,就用Range(0,0,tempdoc)获取一个空的range,只带有格式和创建信息。将所有源数据写 入range后,用temp.write(out)将range中的数据写入新创建的目标文件。
6.如何不续写?
思路:由传入的参数,如果不续写,就用range.replaceText(“”,false),将整个range清空, 然后再往后插入需要的内容。
问题1:角标越界异常
分析:将整个range清空会导致无法插入,可以将整个range改为”
t
o
b
e
d
e
l
e
t
e
d
"
,
输
出
前
再
r
a
n
g
e
.
r
e
p
l
a
c
e
T
e
x
t
(
"
{tobedeleted}”,”“)删掉标志即可。
问题2:做了上述操作后,仍然是续写,原range并没有清空。
分析:经过一系列测试,发现原因:用程序写入的文本,用range.text()读取不到,当然用 range.replaceText也无法操作了。而在程序写入后,随便手动在文件中写点东西然后保存, 再用range.text()就可以读取到了。
结论:这可能是包的固有bug之二,暂时无法解决。
其实,poi包对于word文档来说,主要功能还是读取,写入功能很初级、不完善。poi只能操作最简单的word格式内容,当要求的样式复杂、文档长度较长时,用poi就较难完成要求。
这时,Jacob是一个更好的选择。Jacob能完整保持复杂的格式内容,操作也更为方便。但Jacob也有个缺点:只能在Windows平台下实现,无法在linux平台下实现。
此外,要生成标准格式的word文档,还有一种思路,是在另一篇博客上看到的:
先用office2003或者2007编辑好word的样式,然后另存为xml,将xml翻译为FreeMarker模板,最后用java来解析FreeMarker模板并输出Doc。经测试这样方式生成的word文档完全符合office标准,样式、内容控制非常便利,打印也不会变形,生成的文档和office中编辑文档完全一样。