Java 将数据写⼊word ⽂档(.doc )
Java可⽤org.apache.poi包来操作word⽂档。org.apache.poi包可于上下载,解压后各jar作⽤如下图所⽰:
可根据需求导⼊对应的jar。
⼀、HWPFDocument类的使⽤
⽤HWPFDocument类将数据写到指定的word⽂档中,基本思路是这样的:
- ⾸先,建⽴⼀个HWPFDocument类的实例,关联到⼀个临时的word⽂档;
- 然后,通过Range类实例,将数据写⼊这个word⽂档中;
- 接着,将这个临时的word⽂档通过write函数写⼊指定的word⽂档中。
- 最后,关闭所有资源。
下⾯详细说明各个步骤。
1.构造函数
这⾥要说明⼀下,经过试验,暂时还没到直接在程序中新建⼀个word⽂档并读取的⽅法,只能先创建好temp.doc,然后在程序中读取。(⽤File-createNewFile和ate创建出来的.doc⽂件,都不能被正确读取)
另外,其实选择哪种参数传⼊都是⼀样的,毕竟HWPFDocument关联的word⽂档是⽆法写⼊的,只是当作⼀个临时⽂件。所以,选择最为熟悉的InputStream较为合适。
参数1:InputStream。可将word⽂档⽤FileInputStream流读取,然后传⼊HWPFDocument类。主要⽤于读取word⽂档中的数据。 参数2:POIFSFileSystem。POIFSFileSystem的构造函数参数可以是(File,boolean)【这样的话file必须是已经存在的】,后者为false时可读写。这⾥可以写为
2.Range类
(1)获取Range类实例。
HWPFDocument类中有⼀系列获取Range类实例以操作word⽂档的⽅法。⽐较常⽤的是getRange(),这个⽅法可以获取涵盖整个⽂档的范围,但不包括任何页眉和页脚。此外,还有获取所有⽂本范围的getOverallRange()、获取所有⽂本框的getMainTextboxRange()等等,具体可以根据需求查阅⽂档。HWPFDocument doc = new HWPFDocument(new POIFSFileSystem(new File("temp.doc"),false ));
Range range = Range();
(2)Range类操作word⽂档
Range类中有⼤量获取⽂档数据的⽅法,若有需要可以查阅⽂档。这⾥只说明与写⼊数据有关的⽅法。
1. insertBefore(String),将字符串插⼊到此range的开头。返回值类型:CharacterRun
2. insertAfter(String),将字符串插⼊到此range的结尾。返回值类型:CharacterRun
3. insertTableBefore(short列数, int⾏数),在此range的开头插⼊⼀个指定⾏列数的表。返回值类型:Table
4. text(),获取当前range的所有⽂本。返回值类型:String。虽然不是写⼊数据的⽅法,但是在调试过程中⽐较好⽤。
3.write⽅法
HWPFDocument类中的write⽅法有三种重载形式:(实际上可以理解为writeTo)
参数1:空参数。将本对象关联的word⽂档写⼊另⼀个打开的可写的POIFSFileSystem⽂件中。
参数2:File。将本对象关联的word⽂档写⼊指定的⽂件(newFile)中。如果该⽂件不存在,则创建;若存在,则覆盖。
参数3:OutputStream。将本对象关联的word⽂档写⼊指定的字节输出流中。
可以根据需求选择,但是最好还是选择OutputStream,因为输出流的操作空间更⼤。参数2的newFile不能续写,只能覆盖。 可以将其直接写⼊⽬标⽂件的输出流,也可以先写⼊⼀个字节数组输出流,在通过字节数组输出流写⼊到⽬标⽂件输出流中。
4.关闭资源
- 关闭doc.close();,也即是关闭doc所使⽤的资源”temp.doc”
- 关闭将数据写⼊指定word⽂档的输出流
⼆、代码⽰例
/**
* @description将数据归档到.doc的word⽂档中。数据续写到原⽬标⽂件末尾。
* @param source
* 源⽂件(必须存在!)
* @param sourChs
* 读取源⽂件要⽤的编码,若传⼊null,则默认是GBK编码
* @param target
* ⽬标word⽂档(必须存在!)
*/
public static void storeDoc(File source, String sourChs, File target) {
/*
* 思路: 1.建⽴字符输⼊流,读取source中的数据。 2.在⽬标⽂件路径下new File:temp.doc
* 3.将⽬标⽂件重命名为temp.doc,并⽤HWPFDocument类关联(temp.doc)。
* 3.由temp.doc建⽴Range对象,写⼊source中的数据。 4.建⽴字节输出流,关联target。
* 5.将range中的数据写⼊关联target的字节输出流。
*/
if (!ists()) {
throw new RuntimeException("⽬标⽂件不存在!");
}
if (sourChs == null) {
sourChs = "GBK";
}
BufferedReader in = null;
HWPFDocument temp = null;
BufferedOutputStream out = null;
String path = Parent();
File tempDoc = new File(path, "temp.doc");
try {write的返回值
in = new BufferedReader(new InputStreamReader(new FileInputStream(source), sourChs));
temp = new HWPFDocument(new BufferedInputStream(new FileInputStream(tempDoc)));
out = new BufferedOutputStream(new FileOutputStream(target));
Range range = Range();
String line = null;
range.insertAfter(getDate(12));
range.insertAfter("\r");
while ((line = in.readLine()) != null) {
range.insertAfter(line);
range.insertAfter("\r"); // word中\r是换⾏符
}
range.insertAfter("\r");
range.insertAfter("\r");
range.insertAfter("\r");
temp.write(out);
} catch (UnsupportedEncodingException e) {
// TODO ⾃动⽣成的 catch 块
e.printStackTrace();
} catch (FileNotFoundException e) {
/
/ TODO ⾃动⽣成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO ⾃动⽣成的 catch 块
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// TODO ⾃动⽣成的 catch 块
e.printStackTrace();
}
}
if (temp != null) {
try {
temp.close();
} catch (IOException e) {
// TODO ⾃动⽣成的 catch 块
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// TODO ⾃动⽣成的 catch 块
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:⽤ate(file)静态函数创建POIFSFileSystem实例,然后⽤ HWPFDocument关联。
结果:java.io.FileNotFoundException: no such entry: “WordDocument”, had: []。⽂件 是创建成功了,也⽤POIFSFileSystem成功载⼊,但是HWPFDocument⽆法接收这个参数。
结论:⽬前只能⽤Office软件创建的word⽂档才⾏。也就是说暂时还没到直接在程序中新建 ⼀个word⽂档的⽅法,只能先创建好temp.doc,然后在程序中读取。
2.⽆法写⼊temp.doc
描述:⽤Range()⽅法获取⽂件的整个范围,然后⽤range.insertAfter(String)⽅法 插⼊数据。编译运⾏没有任何异常,但是打开⽂件发现还是原样。
尝试:插⼊数据后⽤()获取当前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……
可以看出,是llections4包不到导致的。导⼊这个包即可。
收获:使⽤外部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.如何不续写?
思路:由传⼊的参数,如果不续写,就⽤placeText(“”,false),将整个range清空, 然后再往后插⼊需要的内容。
问题1:⾓标越界异常
分析:将整个range清空会导致⽆法插⼊,可以将整个range改为”{tobedeleted}”,”“)删掉标志即可。
问题2:做了上述操作后,仍然是续写,原range并没有清空。
分析:经过⼀系列测试,发现原因:⽤程序写⼊的⽂本,⽤()读取不到,当然⽤ pla
ceText也⽆法操作了。⽽在程序写⼊后,随便⼿动在⽂件中写点东西然后保存, 再⽤()就可以读取到了。
结论:这可能是包的固有bug之⼆,暂时⽆法解决。
四、其他
其实,poi包对于word⽂档来说,主要功能还是读取,写⼊功能很初级、不完善。poi只能操作最简单的word格式内容,当要求的样式复杂、⽂档长度较长时,⽤poi就较难完成要求。
这时,Jacob是⼀个更好的选择。Jacob能完整保持复杂的格式内容,操作也更为⽅便。但Jacob也有个缺点:只能在Windows平台下实现,⽆法在linux平台下实现。
此外,要⽣成标准格式的word⽂档,还有⼀种思路,是在另⼀篇博客上看到的:
先⽤office2003或者2007编辑好word的样式,然后另存为xml,将xml翻译为FreeMarker模板,最后⽤java来解析FreeMarker模板并输出Doc。经测试这样⽅式⽣成的word⽂档完全符合office标准,样式、内容控制⾮常便利,打印也不会变形,⽣成的⽂档和office中编辑⽂档完全⼀样。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论