java匯出2007版word(docx格式)freemarker + xml 實現
Freemarker+xml生成docx
原理概述:word從2003版就支援xml格式,而freemarker是java封裝的模板工具,兩者結合也就是在xml中需要動態生成的部分呼叫freemarker的指令(類似於EL表示式),來生成我們需要的資料,再用流輸出檔案,就達到了寫word的效果。
生成word的基本流程圖如下:
1. 生成docx模板和xml模板
生成docx模板
按照專案需要生成固定格式的docx格式的模板。
為方便測試做了個簡單的例子,docx模板的內容如下圖:
生成xml模板
從docx模板中取出word/document.xml,由於docx屬於zip格式,可以用winrar開啟,如圖:
除word資料夾外其它檔案為基本配置檔案,取出word/document.xml(存放word檔案的文字內容)如下圖:
需要把document.xml解壓出來,修改裡面需要從資料庫匯出的資料用freemarker的指令代替,例如${test} 同時可以用遍歷函式
替換完成後相當於生成了xml模板
生成的Xml模板:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 wp14"> <w:body> <w:p w:rsidR="009175C2" w:rsidRDefault="005B5FB3" w:rsidP="005B5FB3"> <w:pPr> <w:pStyle w:val="1"/> <w:jc w:val="center"/> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> <w:bookmarkStart w:id="0" w:name="_GoBack"/> <w:bookmarkEnd w:id="0"/> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t> 這是一個測試Word</w:t> </w:r> </w:p> <w:p w:rsidR="005B5FB3" w:rsidRDefault="005B5FB3" w:rsidP="005B5FB3"> <w:pPr> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> </w:p> <w:p w:rsidR="005B5FB3" w:rsidRDefault="005B5FB3" w:rsidP="005B5FB3"> <w:pPr> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> </w:p> <w:p w:rsidR="005B5FB3" w:rsidRDefault="005B5FB3" w:rsidP="005B5FB3"> <w:pPr> <w:pStyle w:val="a3"/> <w:numPr> <w:ilvl w:val="0"/> <w:numId w:val="1"/> </w:numPr> <w:ind w:firstLineChars="0"/> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t> ${test}</w:t> </w:r> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t xml:space="preserve"> </w:t> </w:r> </w:p> <#list students as s> <w:p w:rsidR="005B5FB3" w:rsidRDefault="005B5FB3" w:rsidP="005B5FB3"> <w:pPr> <w:pStyle w:val="a3"/> <w:numPr> <w:ilvl w:val="0"/> <w:numId w:val="1"/> </w:numPr> <w:ind w:firstLineChars="0"/> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t> ${s} </w:t> </w:r> </w:p> </#list> <w:sectPr w:rsidR="005B5FB3" w:rsidRPr="005B5FB3"> <w:pgSz w:w="11906" w:h="16838"/> <w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0"/> <w:cols w:space="425"/> <w:docGrid w:type="lines" w:linePitch="312"/> </w:sectPr> </w:body> </w:document>
2. 利用freemarker填充資料並生成word檔案
這裡把資料庫中的資料放到map中,把map和xml模板交給freemarker來生成(填充完資料)的xml具體實現方法如下:
package com.hannet.yigehui; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.HashMap; import java.util.Map; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; public class XmlToExcel { private static XmlToExcel tplm = null; private Configuration cfg = null; private XmlToExcel() { cfg = new Configuration(); try { //註冊tmlplate的load路徑 cfg.setClassForTemplateLoading(this.getClass(), "/template/"); } catch (Exception e) { } } private static Template getTemplate(String name) throws IOException { if(tplm == null) { tplm = new XmlToExcel(); } return tplm.cfg.getTemplate(name); } /** * * @param templatefile 模板檔案 * @param param 需要填充的內容 * @param out 填充完成輸出的檔案 * @throws IOException * @throws TemplateException */ public static void process(String templatefile, Map param ,Writer out) throws IOException, TemplateException{ //獲取模板 Template template=XmlToExcel.getTemplate(templatefile); template.setOutputEncoding("UTF-8"); //合併資料 template.process(param, out); if(out!=null){ out.close(); } } }
利用freemarker生成資料
呼叫freemarker中的process方法(上圖中的方法)來填充資料。
例(用1中生成的xml為模板填充資料):
//xml的模板路徑 template/test.xml
String xmlTemplate = "test.xml";
//填充完資料的臨時xml
String xmlTemp = "d:\\temp.xml";
Writer w = new FileWriter(new File(xmlTemp));
//1.需要動態傳入的資料
Map<String,Object> p = new HashMap<String,Object>();
List<String> students = new ArrayList<String>();
students.add("張三");
students.add("李四");
students.add("王二");
p.put("test", "測試一下是否成功");
p.put("students", students);
//2.把map中的資料動態由freemarker傳給xml
XmlToExcel.process(xmlTemplate, p, w);
注:下文中會給出原始碼這裡只是方便理解。
匯出word檔案
利用java的zipFile 和 ZipOutputStream 及zipFile.getInputStream() 來根據docx模板匯出 (需要把word/document.xml檔案替換成(填充完資料)的xml)具體操作流程如下:
package com.hannet.yigehui;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import freemarker.template.TemplateException;
/**
* 其實docx屬於zip的一種,這裡只需要操作word/document.xml中的資料,其他的資料不用動
* @author yigehui
*
*/
public class XmlToDocx {
/**
*
* @param documentFile 動態生成資料的docunment.xml檔案
* @param docxTemplate docx的模板
* @param toFileName 需要匯出的檔案路徑
* @throws ZipException
* @throws IOException
*/
public void outDocx(File documentFile,String docxTemplate,String toFilePath) throws ZipException, IOException {
try {
String fileName = XmlToDocx.class.getClassLoader().getResource("").toURI().getPath()+docxTemplate;
File docxFile = new File(fileName);
ZipFile zipFile = new ZipFile(docxFile);
Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(toFilePath));
int len=-1;
byte[] buffer=new byte[1024];
while(zipEntrys.hasMoreElements()) {
ZipEntry next = zipEntrys.nextElement();
InputStream is = zipFile.getInputStream(next);
//把輸入流的檔案傳到輸出流中 如果是word/document.xml由我們輸入
zipout.putNextEntry(new ZipEntry(next.toString()));
if("word/document.xml".equals(next.toString())){
//InputStream in = new FileInputStream(new File(XmlToDocx.class.getClassLoader().getResource("").toURI().getPath()+"template/test.xml"));
InputStream in = new FileInputStream(documentFile);
while((len = in.read(buffer))!=-1){
zipout.write(buffer,0,len);
}
in.close();
}else {
while((len = is.read(buffer))!=-1){
zipout.write(buffer,0,len);
}
is.close();
}
}
zipout.close();
} catch (URISyntaxException e) {
e.printStackTrace();
}catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
這裡輸出的檔案即為完整的docx格式的word檔案
測試呼叫的程式碼:
public static void main(String[] args) throws IOException, TemplateException {
//xml的模板路徑*/*
String xmlTemplate = "test.xml";
//設定docx的模板路徑 和檔名
String docxTemplate = "template/test.docx";
String toFilePath = "d:\\test.docx";
//填充完資料的臨時xml
String xmlTemp = "d:\\temp.xml";
Writer w = new FileWriter(new File(xmlTemp));
//1.需要動態傳入的資料
Map<String,Object> p = new HashMap<String,Object>();
List<String> students = new ArrayList<String>();
students.add("張三");
students.add("李四");
students.add("王二");
p.put("test", "測試一下是否成功");
p.put("students", students);
//2.把map中的資料動態由freemarker傳給xml
XmlToExcel.process(xmlTemplate, p, w);
//3.把填充完成的xml寫入到docx中
XmlToDocx xtd = new XmlToDocx();
xtd.outDocx(new File(xmlTemp), docxTemplate, toFilePath);
}
呼叫成功後生成的test.docx如下圖:
注:
測試專案的目錄結構:
需要引入的包: