使用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文件的方法的。