使用freemarker导出word文档
最近的项目中,在导出word文档中遇见了一些问题,由于之前的导出word是导出的doc格式的word文档,但是由于使用的freemarker技术,导出的word文档其实就是xml文件,只是将文件后缀名改为.doc
其实实际上还是xml,所以在使用某些工具打开时,遇到无法解析,或者打开后直接显示xml的问题。
所以这里解决这个问题的办法,是导出docx格式的文档,因为docx格式实际上就是zip格式,使用word生成的docx文档,可以直接使用解压缩工具打开,可以直接编辑里面的内容。
这里也展示一下怎么导出doc格式的word文档。
导出doc文件
先新建一个doc文件,制作好模板,这里假设需要导出的是一个表格。制作好表格的格式,然后另存为时保存为xml文件。
另存为之后,编辑xml文件,由于导出表格,这里只有一行数据,需要修改为freemarker的<#list>
标签来生成列表。将xml文件格式化之后,找到对应的李明这一行数据:
<w:tr>
......
<w:tc>
......
<w:p>
......
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:fareast="ZH-CN"/>
</w:rPr>
<w:t>李明</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
......
<w:p>
......
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:fareast="ZH-CN"/>
</w:rPr>
<w:t>男</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
......
<w:p>
......
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>19</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
这里省略了部分代码,使用freemarker语法修改这一段xml文本:
<#list persons as person>
<w:tr>
......
<w:tc>
......
<w:p>
......
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${person.name}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
......
<w:p>
......
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${person.gender}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
......
<w:p>
......
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${person.age}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</#list>
修改完成之后,将文件后缀改为ftl
,将模板文件复制到项目中。之后就可以使用freemarker生成出xml文件,生成之后可以直接讲文件名后缀名改为doc。
这里附上代码(这里是一个maven项目,模板文件在resources目录下):
Person.class
用来生成word文档中的列表对象
public class Person {
private String name;
private String gender;
private Integer age;
public Person() {
}
public Person(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//......Getter、Setter方法省略
}
数据生成的类(在生成docx的方法中依然使用该类)
public final class DataGenerate {
private DataGenerate(){}
public static List<Person> getData() {
List<Person> persons = new ArrayList<>();
persons.add(new Person("小李", "男", 31));
persons.add(new Person("小王", "男", 18));
persons.add(new Person("小红", "女", 21));
return persons;
}
}
生成word文档,这里直接执行main方法即可
public class XmlToDoc {
public static void main(String[] args) throws IOException, TemplateException {
Configuration configuration = new Configuration(Configuration.getVersion());
configuration.setDefaultEncoding("UTF-8");
URL resource = XmlToDoc.class.getClassLoader().getResource("");
if(resource != null){
File resourceDir = new File(resource.getFile());
configuration.setDirectoryForTemplateLoading(resourceDir);
Template template = configuration.getTemplate("docTemplate.ftl");
File docFile = new File(resource.getFile() + "out/导出.doc");
File docDir = docFile.getParentFile();
if(!docDir.exists()) {
docDir.mkdirs();
}
FileOutputStream docFileOut = new FileOutputStream(docFile);
BufferedWriter docWriter = new BufferedWriter(new OutputStreamWriter(docFileOut));
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("title", "标题");
dataMap.put("persons", DataGenerate.getData());
template.process(dataMap, docWriter);
docWriter.flush();
docWriter.close();
}
}
}
执行完成之后,生成的doc文档在maven项目的target/classes/out目录下。
导出docx文件
因为项目之前使用的word导出都是使用的该技术,项目中已经做了很多freemarker模板,在这时替换其他技术的话,会产生很大的工作量。所以在这里依然使用freemarker技术来导出word,用freemarker生成docx文件。使用解压缩工具打开docx文件可以看到里面有个word
目录,进去之后可以看到一个document.xml
文件,这个文件就是word文档的内容。
我们依然使用之前的word文档,保存为docx格式,解压缩打开内部的word/document.xml
文件。会发现文件与上面的导出xml文件有些不同,但是内档内容部分<w:body>
标签中的内容基本一致。所以就尝试这讲wordxml的文件的<w:body>
部分来替换docx的document.xml
的<w:body>
部分。发现替换之后依然可以正常打开docx文件,所以这里就直接讲以前的模板保留<w:body>
中的内容,其余替换成docx的document.xml
的内容来使用。至于导出word的代码也需要修改,由于之前是直接导出xml格式,而这里需要将生成的xml替换docx文件的document.xml
文件,所以这里多了一步zip文件操作。
与导出doc不同的是,这里不仅需要一个ftl
的freemarker模板,同时需要一个空的docx的文件(需要使用该文件来生成word)。
代码如下:
public class XmlToDocx {
@SuppressWarnings("ResultOfMethodCallIgnored")
public static void main(String[] args) throws IOException, TemplateException {
//使用模板生成document.xml文件
Configuration configuration = new Configuration(Configuration.getVersion());
configuration.setDefaultEncoding("UTF-8");
URL resource = XmlToDoc.class.getClassLoader().getResource("");
if(resource != null){
File resourceDir = new File(resource.getFile());
configuration.setDirectoryForTemplateLoading(resourceDir);
Template template = configuration.getTemplate("docxTemplate.ftl");
File tempFile = new File(resource.getFile() + "temp/docxTemp.xml");
File tempDir = tempFile.getParentFile();
if(!tempDir.exists()) {
tempDir.mkdirs();
}
FileOutputStream tempFileOut = new FileOutputStream(tempFile);
BufferedWriter tempWriter = new BufferedWriter(new OutputStreamWriter(tempFileOut));
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("title", "标题");
dataMap.put("persons", DataGenerate.getData());
template.process(dataMap, tempWriter);
tempWriter.flush();
tempWriter.close();
//使用生成的xml文件来替换docx中的document.xml
ZipFile docxTempFile = new ZipFile(resource.getFile() + "template.docx");
File docxFile = new File(resource.getFile() + "out/导出.docx");
File docxDir = docxFile.getParentFile();
if(!docxDir.exists()){
docxDir.mkdirs();
}
if(!docxFile.exists()) {
docxFile.createNewFile();
}
ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(docxFile));
Enumeration<? extends ZipEntry> entries = docxTempFile.entries();
int len = 1;
byte[] buffer = new byte[1024];
while (entries.hasMoreElements()) {
ZipEntry next = entries.nextElement();
InputStream inputStream = docxTempFile.getInputStream(next);
zipOut.putNextEntry(new ZipEntry(next.toString()));
if("word/document.xml".equals(next.toString())) {
FileInputStream in = new FileInputStream(tempFile);
while ((len = in.read(buffer)) != -1){
zipOut.write(buffer, 0, len);
}
in.close();
}else {
while ((len = inputStream.read(buffer)) != -1){
zipOut.write(buffer, 0, len);
}
inputStream.close();
}
}
zipOut.close();
}
}
}
注意: 这里使用doc另存为的xml文本中的<w:body>
内容来替换docx文件下word/document.xml
文件中<w:body>
内容,在我目前使用模板中是可以直接替换,但是不清楚是否有其他不兼容的情况,大家如果要这样使用的话,自行验证。但是直接用docx文件中的document.xml
来直接制作模板,是可以直接使用上面的导出docx文件的方法的。