最近工作需要从数据库查询数据,代码生成word文档,便在网上查询了一下办法。之前类似的工作也曾经做过,当时是在word模板中设置标记,在代码中填充标记。当前网络上简便的方法也是类似,word文档不是凭空生成的,需要先设计一个word模板,在文档中需要从数据库取值的地方,用${参数名}代替(如图一)。
java代码生成Word文档示例
下面提一下主角,freemarker,一个基于模板和源数据来生成目标文本的引擎工具。这个工具的其他功能我们这里不做关心,只需要从官网下载我们需要的jar包即可。http://freemarker.org/freemarkerdownload.html上面目前可以下载2.3.5的版本。引入jar包之后,我们的代码中需要引用:
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
我们再回到word模板,将其另存为xml格式(如图二),文件名可以中文,也可以是英文。然后将xml文件的后缀名修改为ftl格式,就变成了freemarker的标准格式。可以用类似EditPlus等工具打开ftl文件,要编辑表格参数部分,在表格的第二行数据参数部分,搜索和对应的加入list标签(如图三)。
java代码生成Word文档示例 java代码生成Word文档示例 java代码生成Word文档示例
list指令执行在list开始标签和list结束标签(list中间的部分)之间的代码,其中每个值只指定一个参数(比如表格有五行,只需要定义一行的参数就可以了如图一,会自动迭代赋值),对于每次迭代,循环变量将会引用当前项的值。循环变量仅仅存在于list标签体内。图三list标签中的wordList是程序中定义的list名称,word是这个list的别名,别名用来标记参数引用的是哪个list中的值(如图三),所以还需要修改之前表格中的参数 p a r a 等 为 {para}等为 para等为{word.para}格式。
下面是完整的代码:
public class WordTest {
private Configuration configuration = new Configuration();
// 加载模板信息
private Template readWord() {
// 加载文档模板FTL文件所存在的位置
configuration.setClassForTemplateLoading(this.getClass(),"/com/test");
//web工程还可以使用加载方法configuration.setServletContextForTemplateLoading(Object servletContext, String path);
configuration.setDefaultEncoding("UTF-8");
Template tempWord = null;
try {
// 获取模板信息
tempWord = configuration.getTemplate("文档示例.ftl");
} catch (IOException e) {
e.printStackTrace();
}
return tempWord;
}
// 填充模板参数
private void getFillData(Map dataMap) {
// 根据模板中的参数填充内容,可以不按顺序,参数名称要对上
dataMap.put("wordname", "文档标题");
dataMap.put("user", "userName");
dataMap.put("password", "56fdh6ror8");
dataMap.put("wordDescription", "本文档供开发人员和测试人员参考。调用服务请求时,需要先传递开发者编号和开发者密钥,系统会先对编号和密钥解析验证,如果不通过,不会继续处理信息。");
// list的内容对应表格,表格行数与list的size对应,正常应用中list数据从数据库获取,本示例设置一个size=5的list
List> wordList = new ArrayList>();
for (int i = 0; i < 5; i++) {
Map map = new HashMap();
map.put("para", i);
map.put("type", "参数" + i);
if(4 == i){
map.put("empty", "可空");
}else{
map.put("empty", "不可空");
}
wordList.add(map);
}
dataMap.put("wordList", wordList);
}
// 创建新word文档
public void createWord() {
Map dataMap = new HashMap();
// 组装填充模板数据
getFillData(dataMap);
// 文档输出目录
File outFile = new File("WebRoot/word/测试文档.doc");
Writer out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(outFile)));
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
try {
// 模板填充后,输出到指定目录
readWord().process(dataMap, out);
} catch (TemplateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 调用方法
public static void main(String[] args) {
WordTest test = new WordTest();
test.createWord();
}
}
测试代码模板与代码放在同级目录下面,工程应用中可以放到其他目录中使用不同的方法获取,本示例生成的word文档放到了WebRoot目录下(见图四)。
java代码生成Word文档示例
最后生成的目标word文档样式(见图五)。
java代码生成Word文档示例
青年:新产品中的监测模块每天会生成新的监测数据,对于这些监测数据可提供word文档下载。按理说用poi是可以解决的,但是目前的痛点在于,监测数据的种类很多,每种监测数据的word模板也各不相同,并且在将来文档模板内容以及排版样式还会有变化。使用poi的方式,会使工作量剧增,将来扩展也存在很大的问题,大师你看该怎么办?
禅师:这个问题很有意思,目前生成文档的主流方式确实是使用poi。针对你的这种特殊需求确实需要就事论事,我的建议使用freemarker模板生成word。
青年:freemarker是模板引擎,他是把由模板语言写成的模板生成文本文件,思路上是符合需求的。只要在word文档中需要展示的数据用模板语言标记即可,可是问题是freemarker可以把word模板直接生成word文件吗?我的word模板如图:
禅师:肯定不能把word模板直接作为freemarker的模板生成word的。需要把word模板另存为xml文件,用这个xml文件作为freemarker的模板生成word,如图:
我给你一个(demo-mydoc点击下载),只要在template文件夹下放入这个xml模板,运行Test.java文件就可以。
青年:大师,出问题了,我严格按照你的要求运行时发生了如下的错误:
freemarker.core.ParseException: Parsing error in template "company.xml" in line 3, column 20887:
Encountered "<", but was expecting one of:
"false"
"true"
"."
"+"
"-"
"!"
"["
"("
"{"
at freemarker.core.FMParser.generateParseException(FMParser.java:4672)
at freemarker.core.FMParser.jj_consume_token(FMParser.java:4543)
at freemarker.core.FMParser.UnaryExpression(FMParser.java:340)
at freemarker.core.FMParser.MultiplicativeExpression(FMParser.java:452)
at freemarker.core.FMParser.AdditiveExpression(FMParser.java:402)
可是我有时模板中只有一个参数的时候,比如 n a m e , 就 可 以 正 确 的 显 示 , 一 旦 多 个 参 数 的 时 候 就 报 错 , 甚 至 有 时 重 写 一 遍 模 板 , 哪 怕 只 有 一 个 参 数 {name},就可以正确的显示,一旦多个参数的时候就报错,甚至有时重写一遍模板,哪怕只有一个参数 name,就可以正确的显示,一旦多个参数的时候就报错,甚至有时重写一遍模板,哪怕只有一个参数{name}的时候也报错,怎么会时而可以运行时而报错,像躲迷藏似的,确实让人抓狂,大师能不能指点一二。
禅师哈哈一笑后说:年轻人写代码的时候要有耐心,尤其在程序报错的时候更要加倍耐心。我们可以看到报错的原因是因为:freemarker.core.ParseException: Parsing error in template “company.xml” in line 3, column 20887,肯定是模板解析出现问题,也就是解析没成功呗,给你一个查看xml的工具(firstobject xml editor点击下载),看看模板变量是否有问题。
青年用firstobject xml editor工具打开模板文件,按F8格式化,双击左边w:body,定位到该标签如图:
在
这种情况下,freemarker是解析不出模板命令的,改正做法就保留一个完整的
其余的模板命令以此类推。然后把xml模板放到demo中,运行得到如图结果:
禅师:干的不错,看来问题就出在了word模板另存为xml模板的时候,这应该是word软件的一个bug。
青年:感谢大师,现在心情舒畅多了!
总结:
1.word模板另存为xml文档,由于word软件本身的原因,会把模板命令比如$(“name”)分隔成若干块,导致freemarker无法解析。
2.需要用firstobject xml editor工具查看xml模板,手动修改,保留完整的模板命令块。
3.将手动修改好的xml拷进demo中,可成功运行。
4.xml格式的word换行标记