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如下圖:

 

 

 

注:

測試項目的目錄結構:

 

 

需要引入的包:

 

鏈接:https://pan.baidu.com/s/18_koWw1rx5fPTchl0UUMvA 密碼:9nb2

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章