在做项目的过程中,有时候需要把数据生成word文档,常用的有POI、ITEXT等,在这篇文章中我们使用freemarker模板来生成我们想要的word文档。
首先我们需要使用office word来编辑一个word模板文档:
上面的图片中,我们把字段字写成${XXX}的格式,这对应这我们实体类的字段,下面的图片可以任意上传两张图片,因为我们会生成base64格式的一段编码。
接下来我们另存word文档为xml文件,打开后如下所示:
图中选中的部分如果发现被发开了,不是${XXX}的格式,只需把中间的标签删除即可,接下来是图片部分,我们能看到它生成了一段base64位的编码,我们只需要先删除这段编码,然后把图片对应的字段名以上述的格式写进去即可:
接下来是java后台代码,建了一个包放我们编辑好的ftl文件:
下面的代码是图片编码转换器,这里写的是固定路径,实际项目中可以从数据库查询保存的路径放入imgFile即可:
/图片编码转换
public String getImgStr() throws Exception{
// System.out.println("realpath=================:::::::::::"+realpath);
// System.out.println("photo=================:::::::::::"+photo);
String imgFile = "D:\timg.png";
InputStream in = null;
byte[] data = null;
in = new FileInputStream(imgFile);
data = new byte[in.available()];
in.read(data);
in.close();
BASE64Encoder ecoder = new BASE64Encoder();
return ecoder.encode(data);
}
下面是主要代码,它主要用来读取到ftl文件,然后放入我们对应字段的值:
private Configuration configuration = null;
public WordTest() {
configuration = new Configuration();
configuration.setDefaultEncoding("UTF-8");
}
public void createWord() {
Map<String, Object> dataMap = new HashMap<String, Object>();
try {
dataMap.put("name", "张三");
dataMap.put("sex", "男");
dataMap.put("age", "22");
dataMap.put("school", "清华大学");
dataMap.put("age", "22");
dataMap.put("sfzzm", getImgStr());
dataMap.put("sfzfm", getImgStr2());
configuration.setClassForTemplateLoading(this.getClass(), "/template"); //
Template template = configuration.getTemplate("demo.ftl");
File outFile = new File("D:/wordTest.doc");//
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
template.process(dataMap, out);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Template类是主要使用的模板引擎类。接下来写个测试方法,就会在我们指定的d盘下面生成一个叫wordTest.doc的word。
public static void main(String[] args) {
WordTest test = new WordTest();
test.createWord();
}
打开后生成word文档如下:
到此使用freemarker生成word就结束了,由于我们再实际的项目中,下载word会调用浏览器的下载功能进行下载,所以下面贴下处理方法:
// 提示:在调用工具类生成Word文档之前应当检查所有字段是否完整
// 否则Freemarker的模板引擎在处理时可能会因为找不到值而报错 这里暂时忽略这个步骤了
File file = null;
InputStream fin = null;
ServletOutputStream out = null;
try {
// 调用工具类WordUtil的createDoc方法生成Word文档
file = WordUtil.createDoc(map, "userDoc");
fin = new FileInputStream(file);
String fileName = gagl.getSname()+"的沙盘档案报告.doc";
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
// 设置浏览器以下载的方式处理该文件默认名为resume.doc
// response.addHeader("Content-Disposition", "attachment;filename=userDoc.doc");
response.setHeader("Content-Disposition", "attachment;filename="
.concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
out = response.getOutputStream();
byte[] buffer = new byte[1024]; // 缓冲区
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(); // 删除临时文件
}
return null;
接下来就是遍历,如果我们的图片路径放在一个字段里,我们肯定需要分割然后再显示图片,数据也是一样,如果我们的数据有多条,需要遍历之后都显示在word中,那么该怎么做呢,下面简单的介绍下,这部分就不做实例了,直接贴代码:
//已完成技能培训
String skillStrs2 ="";
List<String> skillList = new ArrayList<String>();
String[] skillStrs = teacher.getSkillTheme().split(",");
for(int i=0;i<skillStrs.length;i++){
skillStrs2 = skillStrs[i];
skillList.add(skillStrs2);
}
map.put("skillThemeList", skillList);//技能主题
String hourStrs2 = "";
List<String> hourList = new ArrayList<String>();
String[] hourStrs = teacher.getSkillHours().split(",");
for(int i=0;i<hourStrs.length;i++){
hourStrs2 = hourStrs[i];
hourList.add(hourStrs2);
}
map.put("skillHoursList", hourList); //技能总计小时数
//技能培训证书照
try {
String simgStrs2 ="";
List<String> skillImgList = new ArrayList<String>();
String[] simgStrs = teacher.getSkillImg().split(",");
for(int i=0;i<simgStrs.length;i++){
simgStrs2 = simgStrs[i].substring(14, simgStrs[i].length());
try {
skillImgList.add(getImgStr(simgStrs2));
} catch (Exception e) {
e.printStackTrace();
}
}
map.put("skillImgList", skillImgList);
} catch (Exception e) {
e.printStackTrace();
}
上面的代码中,第一部分是数据的集合,下面是图片集合,我们先进行分割,然后遍历后放入集合里,最后再放入我们需要的map里,这个map对应的key就是ftl里对应的${}字段。
然后我们需要去修改我们的ftl文件:
<#list supervisorTypeList as sups>
<#list supervisorHoursList as shours>
<#list supervisorImgList as supim>
<#assign a=sups_index />
<#assign b=shours_index />
<#assign c=supim_index />
<#if (a=b) >
<#if (b=c) >
<w:p wsp:rsidR="00551926" wsp:rsidRDefault="00551926" wsp:rsidP="003D2B62"><w:pPr><w:jc w:val="left"/><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>心理督导</w:t></w:r><w:r wsp:rsidR="00E90F59"><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>:</w:t></w:r></w:p>
<w:p wsp:rsidR="00A57CB2" wsp:rsidRDefault="00551926" wsp:rsidP="003D2B62"><w:pPr><w:jc w:val="left"/><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>1.</w:t></w:r><w:r wsp:rsidR="00DE7BE6"><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>督导类型:${sups}</w:t></w:r></w:p>
<w:p wsp:rsidR="00DE7BE6" wsp:rsidRDefault="00551926" wsp:rsidP="003D2B62"><w:pPr><w:jc w:val="left"/><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>2.</w:t></w:r><w:r wsp:rsidR="002B42DF" wsp:rsidRPr="002B42DF"><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>总计小时数</w:t></w:r><w:r wsp:rsidR="002B42DF"><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>:${shours}</w:t></w:r></w:p>
<w:p wsp:rsidR="002B42DF" wsp:rsidRDefault="00551926" wsp:rsidP="003D2B62"><w:pPr><w:jc w:val="left"/><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>3.</w:t></w:r><w:r wsp:rsidR="00A97706"><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>证书</w:t></w:r><w:r><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>照片</w:t></w:r><w:r wsp:rsidR="00A97706"><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr><w:t>:</w:t></w:r></w:p><w:p wsp:rsidR="00A97706" wsp:rsidRDefault="00A86982" wsp:rsidP="003D2B62"><w:pPr><w:jc w:val="left"/><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体"/><wx:font wx:val="宋体"/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr></w:pPr><w:r wsp:rsidRPr="00A86982"><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体"/><wx:font wx:val="宋体"/><w:noProof/><w:sz w:val="28"/><w:sz-cs w:val="28"/></w:rPr>
<w:pict>
<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
<v:stroke joinstyle="miter"/>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0"/>
<v:f eqn="sum @0 1 0"/>
<v:f eqn="sum 0 0 @1"/>
<v:f eqn="prod @2 1 2"/>
<v:f eqn="prod @3 21600 pixelWidth"/>
<v:f eqn="prod @3 21600 pixelHeight"/>
<v:f eqn="sum @0 0 1"/>
<v:f eqn="prod @6 1 2"/>
<v:f eqn="prod @7 21600 pixelWidth"/>
<v:f eqn="sum @8 21600 0"/>
<v:f eqn="prod @7 21600 pixelHeight"/>
<v:f eqn="sum @10 21600 0"/>
</v:formulas>
<v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
<o:lock v:ext="edit" aspectratio="t"/>
</v:shapetype>
<w:binData w:name="${"wordml://0200001"+supim_index+1+".jpg"}" xml:space="preserve">${supim}</w:binData>
<v:shape id="图片" o:spid="_x0000_i1025" type="#_x0000_t75" style="width:414.75pt;height:207.75pt;visibility:visible;mso-wrap-style:square">
<v:imagedata src="${"wordml://0200001"+supim_index+1+".jpg"}" o:title="菜单"/>
</v:shape>
</w:pict>
</w:r></w:p>
</#if>
</#if>
</#list>
</#list>
</#list>
上述的代码就是在ftl中使用遍历了,便签都是固定的,有兴趣的可以尝试下遍历,然后显示在word中。
由于需要生成ftl文件,如果字段过多的话,写起来就会很麻烦,看到那么多标签,改起来也是个大工程。这应该是一个弊端。
好了,至此freemarker生成word的代码就结束了。