2010年4月25日星期日

Java 读写 Word doc 文件的方法(POI代码示例)

都说懒人推动社会进步。
我的出发点是,想做个自动生成工作日志的工具。思路是读取google日历中我每日的工作记录,填充生成公司规定格式的word文件,然后再调用邮件函数发送邮件到领导的信箱里面去。

这其中一环就是Java读写doc文件。网上找了不少资料,初步选用了Apache POI,可以读写整个Office系列的各种文档,并且不依赖Office的任何库,纯JAVA代码。直接读写doc的二进制文件,我最喜欢了。相对的有个Java com 桥的方法,用Java来操作com对象,后台运行隐藏的word程序,甚为不喜。

POI API Documentation 的文档写的太差了,没有例子,Apache 的 POI 项目主页也没有代码,直接说想看代码,请去svn下载代码,阅读他的单元测试用例,TestCase。
最后磕磕绊绊写了下面的一个测试的例子。基本可以看清这个POI的hwpf库,读写word doc 文件的方法。代码、注释都是原创,高手飘过去就行了。

import java.io.*;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Section;

public class Main {

public static void main(String[] args) {
try {
//新建 HWPFDocument 对象,读入doc文件
HWPFDocument doc = new HWPFDocument(new FileInputStream("c:\\test.doc"));
//得到整个doc文档的Range,可以理解为文档对象
Range r = doc.getRange();

System.out.println("Example you supplied:");
System.out.println("---------------------");

String text = new String("");
//得到整个文档里面的所有纯文字,包含回车换行。一段是一行
text = r.text();
//System.out.println(text);

//得到整个文档的分节数。一般只有一节,排版很漂亮的word文档一般分为多节
System.out.println("numSections: " + r.numSections());
//得到倒数第一节的Section对象
Section section = r.getSection(r.numSections() - 1);
//得到该节里面的段落数
System.out.println(section.numParagraphs());
System.out.println("numParagraphs: " + section.numParagraphs());

String searchText = "${Ryan}";
String replacementText = "Apache Software Foundation";

//循环得到每一段落的文字。这个跟Range.text()是不同的。
for (int np = 0; np < section.numParagraphs(); np++) {
Paragraph para = section.getParagraph(np);
//得到该段落的文字
text = para.text();
//System.out.println(Integer.toString(np) + ":" + text);
int offset = text.indexOf(searchText);
if (offset >= 0) {
System.out.println(Integer.toString(np) + ":" + para.text());
//如果找到了,就进行文字的替换。replaceText只能针对段落
para.replaceText(searchText, replacementText);
break;
}
}
//写入到新的doc文件
OutputStream outdoc = new FileOutputStream("c:\\test2.doc");
doc.write(outdoc);
outdoc.flush();
outdoc.close();

} catch (Throwable t) {
t.printStackTrace();
}
}
}


编译运行很顺利,但是悲剧的是,最后调用write方法写入的doc文件打不开了,报格式错误,用Notepad++打开这个doc文件对比原来的,发现文字确实替换成功了,但是文件的尾巴上少了一部分内容,二进制和assic混合的,格式看来是被破坏了。

忙活了半天,最后发现杯具了。应该是兼容性的问题。改天去他们的邮件列表问问。或者路过的看官知道的,指导一下我。

我在写这个文章的时候,又想到一个绝妙的注意。何必绕道这么远呢?
实施工作日志内容来源于google calendar,如果能够利用google app doc 自己本身的宏:google script,的功能,读取日历数据,形成doc文件,调用gmail发送到指定的信箱,不是齐活了吗?费不着用Java了。

其实在决定用java之前,想过用VB,这个做肯定不存在问题,VB操作com对象本身是轻车熟路,顶多再找个发邮件的库,实在不行了没人手工发送。方法还是很多的。

科技以人为本。