可以直接看主要代码实现
doc作为模板文件生成指定格式的doc文件
实现逻辑
1、把作为模板的doc文件另存为xml文件
2、凡是需要填充的数据用${xxxx}替代
3、利用Template类将数据填充到模板并生成文件
代码:
/**
* 将数据以特定模板格式输出到word文档(目前仅支持输出doc文件,只能通过代码修改文字内容)
* @param data 输入模板数据
* @param templatePath 模板存放路径
* @param templateName 模板名称(XXX.xml,由doc/docx文档转换而成)
* @param exFilePath 输出文件路径
* @param exFileName 输出文件名称(XXX.doc)
* @return
*/
public static boolean createDoc(Map<String,Object> data,String templatePath,String templateName,String exFilePath,String exFileName) {
boolean result = false;
Writer out = null;
URL basePath = WordUtils.class.getClassLoader().getResource("");//获取类文件所在根目录,注意是编译后的class文件目录
try {
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("UTF-8");
configuration.setDirectoryForTemplateLoading(new File(basePath.getPath() + templatePath));
Template t = configuration.getTemplate(templateName); //获取模板文件
File file = new File(basePath.getPath() + exFilePath);//生成生成文件所在目录
if (!file.exists() && !file.isDirectory()) {
file.mkdirs();
}
File outFile = new File(basePath.getPath() + exFilePath + separator + exFileName); //导出文件生成
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile)));
t.process(data, out); //将填充数据填入模板文件并输出到目标文件
result = true;
} catch (Exception e) {
e.printStackTrace();
} finally{
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
docx作为模板文件生成自定义格式的doc/docx文件
实现逻辑
1、把doc/docx文档修改为ZIP格式(直接修改后缀)
2、取出
word/document.xml(主要内容)
[Content_Types].xml、\word_rels\document.xml.rels(图片配置)
word/header1.xml(页眉)
……
3、根据需要替换的内容修改以上文件,具体见替换文件说明
4、以zip文件的形式处理模板文件(doc/docx) ,遍历该zip,将zip内除了media目录下的文件(图片另外处理)和替换文件(步骤3内文件)的每个文件输出到指定文件(doc/docx,以zip流的形式输入)
String xmlDocumentXmlRelsComment = FreeMarkUtils.getFreemarkerContent(dataMap, xmlDocumentXmlRels, templatePath);
ByteArrayInputStream documentXmlRelsInput = new ByteArrayInputStream(xmlDocumentXmlRelsComment.getBytes());//替换的xml
ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream("D:/workplace/springBootWorkplace/myDemo/target/classes/templates/out.docx"));
ZipFile zipFile = new ZipFile("D:/workplace/springBootWorkplace/myDemo/target/classes/templates/docTemplates.docx");//以zip文件的形式处理docx文件
Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();//遍历zip文件内的所有文件
int len = -1;
byte[] buffer = new byte[1024];
while (zipEntrys.hasMoreElements()) {
ZipEntry next = zipEntrys.nextElement();//以枚举方式获取文件
InputStream is = zipFile.getInputStream(next);//用于文件输出
zipout.putNextEntry(new ZipEntry(next.getName()));//开始写入,设置文件名称
if (documentXmlRelsInput != null) {
while ((len = documentXmlRelsInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
documentXmlRelsInput.close();
}else{
while ((len = is.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
}
is.close();
}
zipout.close();
zipFile.close();
5、将替换文件和图片文件(word/media)以zip流形式输入到指定文件
6、关闭流
word转换zip后替换文件说明
1、\word\document.xml
用来存在word文档的主要数据信息
2、\word_rels\document.xml.rels
用来存在word文档的主要数据配置 包括图片的指向
<Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.jpeg"/>
rId7:唯一标识,image1.jpeg:图片名称
与对应的document.xml
<wp:docPr id="2" name="图片 2" descr="D:\阿里工作\装修\装修\北欧1\1.jpg"/>
id:唯一标识,name:标识,descr:图片路径
<a:blip r:embed="rId7">
r:embed对应document.xml.rels的Id
<w:p w:rsidR="00EC2516" w:rsidRPr="00EC2516" w:rsidRDefault="00EC2516" w:rsidP="00EC2516">
<w:r w:rsidRPr="00EC2516">
<w:rPr>
<w:noProof/>
</w:rPr>
<w:drawing>
<wp:inline distT="0" distB="0" distL="0" distR="0">
<wp:extent cx="5274310" cy="7024797"/>
<wp:effectExtent l="0" t="0" r="2540" b="5080"/>
<wp:docPr id="2" name="图片 2" descr="D:\阿里工作\装修\装修\北欧1\1.jpg"/>
<wp:cNvGraphicFramePr>
<a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"></a:graphicFrameLocks>
</wp:cNvGraphicFramePr>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvPicPr>
<pic:cNvPr id="0" name="Picture 2" descr="D:\阿里工作\装修\装修\北欧1\1.jpg"/>
<pic:cNvPicPr>
<a:picLocks noChangeAspect="1" noChangeArrowheads="1"/>
</pic:cNvPicPr>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId7">
<a:extLst>
<a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
<a14:useLocalDpi xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" val="0"></a14:useLocalDpi>
</a:ext>
</a:extLst>
</a:blip>
<a:srcRect/>
<a:stretch>
<a:fillRect/>
</a:stretch>
</pic:blipFill>
<pic:spPr bwMode="auto">
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="5274310" cy="7024797"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
<a:noFill/>
<a:ln>
<a:noFill/>
</a:ln>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
3、\word\header1.xml
用来配置docx文档的页眉文件
页眉:Word文档模板测试ymdhis
<w:p w:rsidR="00DF53B8" w:rsidRDefault="00DF53B8" w:rsidP="00DF53B8">
<w:pPr>
<w:pStyle w:val="a3"/>
</w:pPr>
<w:r>
<w:t>Word</w:t>
</w:r>
<w:r>
<w:t>文档模板测试</w:t>
</w:r>
<w:r w:rsidR="00CC7E39">
<w:t>ymdhis</w:t>
</w:r>
</w:p>
4、[Content_Types].xml
用来配置 docx文档中所插入图片的类型 如 png、jpeg、jpg等
,XX的格式要与插入文件的格式一致,每种类别加一条记录
<Default Extension="jpeg" ContentType="image/jpeg"/>
代码
pom.xml
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
/**
* 获取当前日期的字符串(毫秒) 如
* @return
*/
public static String getCurrentTime_yyyyMMddHHmmssSSS(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
return sdf.format(new Date());
}
/**
* 将模板填充完数据以字符串形式输出
* @param dataMap 参数
* @param templateName 模板名称
* @param temp_path 模板路径 classes下的路径 如果 classes/templates 传入 /templates即可
* @return
*/
public static String getFreemarkerContent(Map dataMap, String templateName, String temp_path) {
String result = "";
try {
//创建配置实例
Configuration configuration = new Configuration();
//设置编码
configuration.setDefaultEncoding("UTF-8");
//ftl模板文件统一放至 com.lun.template 包下面
// configuration.setDirectoryForTemplateLoading(new File("D:/idea_workspace/alarm/alarm/src/main/resources/template/"));
configuration.setClassForTemplateLoading(FreeMarkUtils.class, temp_path);
//获取模板
Template template = configuration.getTemplate(templateName);
StringWriter swriter = new StringWriter();
//将填充数据填入模板文件并输出到目标文件/流
template.process(dataMap, swriter);
result = swriter.toString();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 将模板填充完数据以流形式输出
* @param dataMap 参数
* @param templateName 模板名称
* @param tempPath 模板路径 classes下的路径 如果 classes/templates 传入 /templates即可
* @return
*/
public static ByteArrayInputStream getFreemarkerContentInputStream(Map dataMap, String templateName, String tempPath) {
ByteArrayInputStream in = null;
try {
//创建配置实例
Configuration configuration = new Configuration();
//设置编码
configuration.setDefaultEncoding("UTF-8");
//ftl模板文件统一放至 com.lun.template 包下面
// configuration.setDirectoryForTemplateLoading(new File("D:/idea_workspace/alarm/alarm/src/main/resources/template/"));
configuration.setClassForTemplateLoading(FreeMarkUtils.class, tempPath);
//获取模板
Template template = configuration.getTemplate(templateName);
StringWriter swriter = new StringWriter();
//生成文件
template.process(dataMap, swriter);
//String result = swriter.toString();
in = new ByteArrayInputStream(swriter.toString().getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return in;
}
**
主要代码实现
**
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* 注意该方法所在工程是springboot结构,模板放置于\src\main\resources\templates\下,其他工程结构要将读取路径对应修改
* frameMark:${title}
* @author Administrator
*
*/
public class WordUtils {
private final static String separator = File.separator;
private final static String suffix_docx = "docx";
private final static String suffix_doc = "doc";
/**
* 将数据以特定模板格式输出到word文档(目前仅支持输出doc文件,只能通过代码修改文字内容)
* @param data 输入模板数据
* @param templatePath 模板存放路径
* @param templateName 模板名称(XXX.xml,由doc/docx文档转换而成)
* @param exFilePath 输出文件路径
* @param exFileName 输出文件名称(XXX.doc)
* @return
*/
public static boolean createDoc(Map<String,Object> data,String templatePath,String templateName,String exFilePath,String exFileName) {
boolean result = false;
Writer out = null;
URL basePath = WordUtils.class.getClassLoader().getResource("");//获取类文件所在根目录,注意是编译后的class文件目录
try {
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("UTF-8");
configuration.setDirectoryForTemplateLoading(new File(basePath.getPath() + templatePath));
Template t = configuration.getTemplate(templateName); //获取模板文件
File file = new File(basePath.getPath() + exFilePath);//生成生成文件所在目录
if (!file.exists() && !file.isDirectory()) {
file.mkdirs();
}
File outFile = new File(basePath.getPath() + exFilePath + separator + exFileName); //导出文件生成
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile)));
t.process(data, out); //将填充数据填入模板文件并输出到目标文件
result = true;
} catch (Exception e) {
e.printStackTrace();
} finally{
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* 以docx为模板生成doc/docx文件
* 模板文件的配置文件由docx转换成zip获取,docx文档生成工具类 (改变后缀名即可)
* 在使用制作模板的过程中如果模板中有图片那就保留图片,修改[Content_Types].xml和document.xml.rels文档,如果模板中没有图片 则不需要设置[Content_Types].xml和document.xml.rels
* 由于word模板的个性化 所以 每次做模板都要重新覆盖原来的模板
* @param dataMap 参数数据
* @param docxTemplateFile docx模主板名称(注意模板文件只能为docx,doc不能被作为zip读取)
* @param xmlDocument docx中document.xml模板文件 用来存放word文档的主要数据信息
* @param xmlDocumentXmlRels docx中document.xml.rels 模板文件 用来存放word文档的主要数据配置 包括图片的指向
* @param xmlContentTypes docx中 [Content_Types].xml 模板文件 用来配置 docx文档中所插入图片的类型 如 png、jpeg、jpg等
* @param xmlHeader docx中 header1.xml 模板文件 用来配置docx文档的页眉文件
* @param templatePath 模板存放路径 如 /templates/
* @param outputFileTempPath 所生成的docx文件的临时路径文件夹 如果 temp/20180914051811/
* @param outputFileName 所生成的docx文件名称 如 xxx.docx 或 xxx.doc
* @throws Exception
*/
public static void createDocx(Map<String, Object> dataMap, String docxTemplateFile, String xmlDocument, String xmlDocumentXmlRels,
String xmlContentTypes, String xmlHeader, String templatePath,
String outputFileTempPath, String outputFileName) throws Exception {
URL basePath = WordUtils.class.getClassLoader().getResource("");//获取类文件所在根目录,注意是编译后的class文件目录
String realTemplatePath = basePath.getPath() + templatePath;
//临时文件产出的路径
String outputPath = basePath.getPath() + outputFileTempPath;
try {
//================================获取 document.xml.rels 输入流================================
String xmlDocumentXmlRelsComment = FreeMarkUtils.getFreemarkerContent(dataMap, xmlDocumentXmlRels, templatePath);
ByteArrayInputStream documentXmlRelsInput = new ByteArrayInputStream(xmlDocumentXmlRelsComment.getBytes());
//================================获取 header1.xml 输入流================================
ByteArrayInputStream headerInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlHeader, templatePath);
//================================获取 [Content_Types].xml 输入流================================
ByteArrayInputStream contentTypesInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlContentTypes, templatePath);
//读取 document.xml.rels 文件 并获取rId 与 图片的关系 (如果没有图片 此文件不用编辑直接读取就行了)
Document document = DocumentHelper.parseText(xmlDocumentXmlRelsComment);
Element rootElt = document.getRootElement(); // 获取根节点
Iterator iter = rootElt.elementIterator();// 获取根节点下的子节点head
List<Map<String, String>> picList = (List<Map<String, String>>) dataMap.get("picList");
// 遍历Relationships节点,获取Target类型为media的节点(该节点由frameMark生成),将其Id值映射到picList的子项,对应rId
while (iter.hasNext()) {
Element recordEle = (Element) iter.next();
String id = recordEle.attribute("Id").getData().toString();
String target = recordEle.attribute("Target").getData().toString();
if (target.indexOf("media") == 0) {
for (Map<String, String> picMap : picList) {
if (target.endsWith(picMap.get("name"))) {
picMap.put("rId", id);
}
}
}
}
dataMap.put("picList", picList);//覆盖原来的picList,主要为了关联 document.xml.rels内的图片配置声明;
//================================获取 document.xml 输入流================================
ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlDocument, templatePath);
File docxFile = new File(realTemplatePath + separator + docxTemplateFile);
if (!docxFile.exists()) {
docxFile.createNewFile();
}
ZipFile zipFile = new ZipFile(docxFile);//模板文件只能为docx类型,doc类型不能被作为zip读取
Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();//用于遍历压缩文件内的每个文件
File tempPath = new File(outputPath);
//如果输出目标文件夹不存在,则创建
if (!tempPath.exists()) {
tempPath.mkdirs();
}
ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(outputPath + outputFileName));
//================================覆盖文档-begin================================
//逻辑说明:将模板文件(doc/docx)以zip形式处理,遍历该zip,将zip内除了media目录下的文件(图片另外处理)的每个文件输入到指定文件(doc/docx,以zip形式输入),其中[Content_Types].xml、document.xml.rels、word/document.xml、word/header1.xml用frameMark生成的文件替代
int len = -1;
byte[] buffer = new byte[1024];
while (zipEntrys.hasMoreElements()) {
ZipEntry next = zipEntrys.nextElement();//以枚举方式获取文件
InputStream is = zipFile.getInputStream(next);
if (next.toString().indexOf("media") < 0) {
// 把输入流的文件传到输出流中 如果是word/document.xml由我们输入
zipout.putNextEntry(new ZipEntry(next.getName()));//开始写入,设置文件名称
// System.out.println("next.getName()>>>" + next.getName() + " next.isDirectory()>>>" + next.isDirectory());
//写入图片配置类型
if (next.getName().equals("[Content_Types].xml")) {
if (contentTypesInput != null) {
while ((len = contentTypesInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
contentTypesInput.close();
}
} else if (next.getName().indexOf("document.xml.rels") > 0) {
//写入填充数据后的主数据配置信息
if (documentXmlRelsInput != null) {
while ((len = documentXmlRelsInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
documentXmlRelsInput.close();
}
} else if ("word/document.xml".equals(next.getName())) {
//写入填充数据后的主数据信息
if (documentInput != null) {
while ((len = documentInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
documentInput.close();
}
} else if ("word/header1.xml".equals(next.getName())) {
//写入填充数据后的页眉信息
if (headerInput != null) {
while ((len = headerInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
headerInput.close();
}
} else {//其余文件直接输出到导出文件
while ((len = is.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
is.close();
}
}
}
//------------------写入新图片-start------------------
len = -1;
if (picList != null && !picList.isEmpty()) {
for (Map<String, String> pic : picList) {
ZipEntry next = new ZipEntry("word" + separator + "media" + separator + pic.get("name"));
zipout.putNextEntry(new ZipEntry(next.toString()));
InputStream in = new FileInputStream(pic.get("path"));
while ((len = in.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
in.close();
}
}
//------------------写入新图片-over------------------
//================================覆盖文档-end================================
zipout.close();
zipFile.close();
} catch (Exception e) {
e.printStackTrace();
throw new Exception("生成word文件失败!");
}
}
public static void main(String[] args) {
//============================================createDocx(docx模板测试)-begin============================================
String timeStr1 = DateUtils.getCurrentTime_yyyyMMddHHmmssSSS();
String templatePath1 = separator + "templates" + separator;
String outputFileTempPath1 = "temp" + separator + "doc" + separator + timeStr1 + separator;
String outputFileName1 = timeStr1 + "."+suffix_doc;
Map<String,Object> dataMap1 = new HashMap<String,Object>();
dataMap1.put("name", "王大壮");
dataMap1.put("age", "30");
dataMap1.put("phone", "18450098635");
dataMap1.put("mailbox", "12345678911");
System.out.println(createDoc(dataMap1, templatePath1, "docTemplates.xml", outputFileTempPath1,outputFileName1));
//============================================createDocx(docx模板测试)-end============================================
//============================================createDocx(docx模板测试)-begin============================================
URL basePath = WordUtils.class.getClassLoader().getResource("");
String picPath = basePath.getPath() + separator + "templates" + separator;
;
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("ymdhis", DateUtils.getCurrentTime_yyyyMMddHHmmss());
List<String> listTile = new ArrayList<>();
listTile.add("这是第一个标题");
listTile.add("这是第二个标题");
listTile.add("这是第三个标题");
dataMap.put("listTitle", listTile);
List<String> picTypes = new ArrayList<>();
picTypes.add("jpg");
dataMap.put("picTypes", picTypes);
List<Map<String, String>> picList = new ArrayList<>();
Map<String, String> picMap = new HashMap<>();
// 要按顺序
picMap.put("path", picPath + "pic1.jpg");
picMap.put("name", "pic1.jpg");
picList.add(picMap);
picMap = new HashMap<>();
picMap.put("path", picPath + "pic2.jpg");
picMap.put("name", "pic2.jpg");
picList.add(picMap);
picMap = new HashMap<>();
picMap.put("path", picPath + "pic3.jpg");
picMap.put("name", "pic3.jpg");
picList.add(picMap);
dataMap.put("picList", picList);
List<Map<String, Object>> listTaleData = new ArrayList<>();
Map<String, Object> map = new HashMap<>();
map.put("name", "小明");
map.put("age", "11");
map.put("sex", "男");
map.put("grade", "五年级");
listTaleData.add(map);
map = new HashMap<>();
map.put("name", "小红");
map.put("age", "12");
map.put("sex", "女");
map.put("grade", "六年级");
listTaleData.add(map);
map = new HashMap<>();
map.put("name", "小花");
map.put("age", "13");
map.put("sex", "女");
map.put("grade", "七年级");
listTaleData.add(map);
dataMap.put("listTaleData", listTaleData);
dataMap.put("summary", "总结总结总结!");
String timeStr = DateUtils.getCurrentTime_yyyyMMddHHmmssSSS();
String docxTemplateFile = "docxTemplates.docx";
String xmlDocument = "document.xml";
String xmlDocumentXmlRels = "document.xml.rels";
String xmlContentTypes = "[Content_Types].xml";
String xmlHeader = "header1.xml";//可以用来修改页眉的一些信息
String templatePath = separator + "templates" + separator;
String outputFileTempPath = "temp" + separator + "docx" + separator + timeStr + separator;
String outputFileName = timeStr + "."+suffix_docx;
String outputFileName2 = timeStr + "."+suffix_doc;
try {
createDocx(dataMap, docxTemplateFile, xmlDocument, xmlDocumentXmlRels, xmlContentTypes,
xmlHeader, templatePath, outputFileTempPath, outputFileName);
createDocx(dataMap, docxTemplateFile, xmlDocument, xmlDocumentXmlRels, xmlContentTypes,
xmlHeader, templatePath, outputFileTempPath, outputFileName2);
} catch (Exception e) {
e.printStackTrace();
}
//============================================createDocx(docx模板测试)-end============================================
}
}
模板所在目录
模板文件
链接: https://pan.baidu.com/s/1ocavyv7wfB_bvptO3Ten3w 提取码: fgwp