需求:
①某单位有多个部门,要求导出一个word文档,每个部门生成如下申报表格
②干工程的一个小伙伴因为换领导,要把40个月*30天的日报的内容从旧模板更换成新的模板
两个的基本思路一样,获得数据,塞到模板里,都可以使用使用freemarker,在这里记录一下主要内容。
org.freemarker
freemarker
2.3.20
其中
exportMillCertificateWord是以浏览器下载的方式生成文件
exportSaveMillCertificateWord是导出到本地的方式生成文件
public class FreeMarkerWord {
private static Configuration configuration = null;
//获取模板文件的服务器位置
//private static final String templateFolder = WordUtils.class.getClassLoader().getResource("../../").getPath() + "WEB-INF/templetes/";
private static final String templateFolder = "H:/model";
static {
configuration = new Configuration();
configuration.setDefaultEncoding("utf-8");
try {
//根据位置加载模板
configuration.setDirectoryForTemplateLoading(new File(templateFolder));
} catch (IOException e) {
e.printStackTrace();
}
}
private FreeMarkerWord() {
throw new AssertionError();
}
//浏览器下载方式 map:数据;title:生成的文件名称;ftlFile:模板文件名称;
public static void exportMillCertificateWord(HttpServletRequest request, HttpServletResponse response, Map map,String title,String ftlFile) throws IOException {
//根据文件名在模板文件夹路径中找到模板
Template freemarkerTemplate = configuration.getTemplate(ftlFile);
File file = null;
InputStream fin = null;
ServletOutputStream out = null;
try {
// 调用工具类的createDoc方法生成Word文档
file = createDoc(map,freemarkerTemplate);
fin = new FileInputStream(file);
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
String fileName = title+MyUtils.getTimestampNow() + ".doc";
//Content-Disposition 设置浏览器以下载的方式处理该文件名
response.setHeader("Content-Disposition", "attachment;filename="
.concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
out = response.getOutputStream();
byte[] buffer = new byte[512];// 缓冲区
int bytesToRead = -1;
// 通过循环将读入的Word文件的内容输出到浏览器中
while((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} finally {
if(fin != null) fin.close();
if(out != null) out.close();
if(file != null) file.delete(); // 删除临时文件
}
}
//导出导本地方式
public static void exportSaveMillCertificateWord(Map map,String title,String ftlFile) throws IOException {
//根据文件名在模板文件夹路径中找到模板
Template freemarkerTemplate = configuration.getTemplate(ftlFile);
File file = null;
InputStream fin = null;
FileOutputStream fos = null;
//导出的文件.doc
String newTitle = ("H:/newFile/"+title).substring(0,("H:/newFile/"+title).length()-1);
try {
//根据模板生成临时文件
file = createDoc(map,freemarkerTemplate);
//读取生成的文件
fin = new FileInputStream(file);
int tempByte;
byte[] buffer = new byte[512];//缓冲区
fos = new FileOutputStream(newTitle);
//将读取的内容放到缓冲区并且写到新文件中去
while((tempByte = fin.read(buffer))!=-1){
fos.write(buffer,0,tempByte);//数组,开始位置,长度
}
} finally {
if(fin != null) fin.close();
if(fos != null) fos.close();
if(file != null) file.delete(); // 删除临时文件
}
}
//创建word文档
private static File createDoc(Map, ?> dataMap, Template template) {
String name = "sellPlan.doc";
File f = new File(name);
Template t = template;
try {
// 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
t.process(dataMap, w);
w.close();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
return f;
}
}
通过查询数据库方式获取数据,简单写一下map的拼装,在模板中key匹配的位置,塞入对应的value值,循环的结构用list,在word文档中循环输出。
//业务要求:一个word文件中,每个部门为单独的【标题】+【table】
List resultList = new ArrayList();
for(循环其中一个table的所有行){
//行的其他的数据(sql查询封装好的,否则跟下面的目标名称一样,put每个字段)
resultOneOne.put("goalList", goalList);
//行的【目标名称】
resultOneOne.put("targetName", map2.get("name"));
targetList.add(resultOneOne);
}
//每个部门的标题
resultOne.put("title", year + "年度" +map.get("text")+ "单位业务职能目标完成情况申报表");
//每个部门的table
resultOne.put("targetList", targetList);
//所有部门的集合
resultList.add(resultOne);
通过读取word文档的方式获取数据,封装到list中,最后循环list生成list.size()个文件。
File dir = new File("H:/oldFile");
File[] files = dir.listFiles();
List filNameList = new ArrayList();
if (files != null) {
for (int i = 0; i < files.length; i++) {
String fileName = files[i].getName();
filNameList.add(fileName);
}
}
List contentMapList = new ArrayList();
List troubleList = new ArrayList();
for(int i = 0; i < filNameList.size(); i++){
String fileName = filNameList.get(i);
//读取旧版文档
String path = "H:/oldFile/"+fileName;
String buffer = "";
HashMap resultOne = new HashMap();
HashMap map = new HashMap();
try {
if (path.endsWith("docx")) {
OPCPackage opcPackage = POIXMLDocument.openPackage(path);
POIXMLTextExtractor extractor = new XWPFWordExtractor(opcPackage);
//获取到所有内容(没有格式)
buffer = extractor.getText();
//处理内容并封装成map
String month = buffer.substring(buffer.indexOf("年")+1,buffer.indexOf("月"));
map.put("month", month);
extractor.close();
} else {
System.out.println("此文件不是word文件!");
}
} catch (Exception e) {
//记录转换错误的文件 System.out.println("=================================================="+fileName);
troubleList.add(fileName);
e.printStackTrace();
}
在word中整理好格式,另存为xml格式,并保存到代码中对应的路径下,使用foxe_CHS打开并按F8格式化,对需要插入数据的内容,左侧Wbody下面是主要内容标签,右侧${}内是map的key,输出时显示的是value的内容,<#list>循环的标签,类似于for循环,例子中第一层循环标识:每个部门循环生成【标题】+【table】,第二层循环表示每个【table】中循环的条目;_index类似于for循环中的下表,
调用工具类的方法,FreeMarkerWord.exportMillCertificateWord(request,response,map,title,modelFile);
map:为上面拼装的数据;title:生成的目标文件名称;ftlFile:模板文件名称;
在实际开发中遇到的几个小问题
1、数据中包含<>标签,会解析错误,插入<#escape x as x?html> 使其作为文本输出,可以将特殊字符转移。大小比较符号使用lt、lte、gt和gte来替代<、<=、>和>= 也可以使用括号<#if (x>y)> 可以用于比较数字和日期