为了导出docx格式看了等多文档,最后做个总结依赖包用到dom4j和freemarker,最为方便。
<!-- https://mvnrepository.com/artifact/freemarker/freemarker -->
<dependency>
<groupId>freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
0.主要目的:将这样一个页面导出为word文档为doc格式,包含一些文本和循环遍历出来的echarts图表。

1.新建一个word文档(docx格式)带水印,生成模板内容,例如下面这种。

水印添加位置(可自己百度)

整体思路
-保存后,复制出来一份,
-修改后缀名为zip。
-解压到一个文件夹中。

-打开文件夹看到如下目录

主要思路:
-获取word里的document.xml文档以及_rels文件夹下的document.xml.rels文档
-把内容填充到document.xml里,以及图片配置信息填充至document.xml.rels文档里
-把水印内容填充到header1.xml,header2.xml,header3.xml里,以及图片配置信息填充至document.xml.rels文档里
-在输入docx文档的时候把填充过内容的的 document.xml、document.xml.rels用流的方式写入zip(详见下面代码)。
-把图片写入zip文件下word/media文件夹中
-输出docx文档(因为word文档本身就是ZIP格式实现的)
2. 目录结构如下:主要文件由上一步拷贝过来的

- document.xml里存放主要数据
- media存放图片信息
- _rels里存放配置信息
document.xml中存放图片的模板主要内容

3.document.xml修改模板内容加上freemarker遍历map集合,填入数据


4.document.xml.rels修改模板引用内容
注意:这里图片配置信息是根据 rId来获取的。docx模板总的${mdl.rId}就是rId的具体值。
为了避免重复,我的图片rId从12开始(在我没有修改之前,里面最大的rId是rId12)。

5.header1.xml,header2.xml,header3.xml页眉 ,修改红框内容即可

6.[Content_Types].xml文件模板

7.还需要在document.xml的最后中加入红框内容,header.xml文件的位置对应的r:id序号

8.工具类代码如下
package com.sl.utils.office.word;
import com.sl.utils.date.DateUtils;
import com.sl.utils.freemark.FreeMarkUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
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;
/**
* docx、doc文档生成工具类 (改变后缀名即可)
* 在使用制作模板的过程中如果模板中有图片那就保留图片,注意[Content_Types].xml和document.xml.rels文档
* 如果模板中没有图片 则不需要设置[Content_Types].xml和document.xml.rels
* 由于word模板的个性化 所以 每次做模板都要重新覆盖原来的模板
*
*
*
* gaoxueyong
*/
public class WordUtils {
private final static String separator = File.separator;
private final static String suffix_docx = "docx";
private final static String suffix_doc = "doc";
/*
* @param dataMap 参数数据
* @param docxTemplateFile docx模主板名称
* @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
* */
public static void createDocx(Map dataMap, String docxTemplateFile, String xmlDocument, String xmlDocumentXmlRels,
String xmlContentTypes, String xmlHeader,String xmlHeader2,String xmlHeader3, String templatePath,
String outputFileTempPath, String outputFileName) throws Exception {
URL basePath = WordUtils.class.getClassLoader().getResource("");
// System.out.println("basePath.getPath() ==> " + basePath.getPath());
String realTemplatePath = basePath.getPath() + templatePath;
//临时文件产出的路径
String outputPath = basePath.getPath() + outputFileTempPath;
List<String> delFileList = new ArrayList<>();
try {
//================================获取 document.xml.rels 输入流================================
String xmlDocumentXmlRelsComment = FreeMarkUtils.getFreemarkerContent(dataMap, xmlDocumentXmlRels, templatePath);
ByteArrayInputStream documentXmlRelsInput =
new ByteArrayInputStream(xmlDocumentXmlRelsComment.getBytes());
//================================获取 document.xml.rels 输入流================================
//================================获取 header1.xml 输入流================================
ByteArrayInputStream headerInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlHeader, templatePath);
ByteArrayInputStream header2Input = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlHeader2, templatePath);
ByteArrayInputStream header3Input = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlHeader3, templatePath);
//================================获取 header1.xml 输入流================================
//================================获取 [Content_Types].xml 输入流================================
ByteArrayInputStream contentTypesInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlContentTypes, templatePath);
//================================获取 [Content_Types].xml 输入流================================
//读取 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("modelList");
// 遍历Relationships节点
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) {
// System.out.println("id>>>"+id+" >>>"+target);
// id>>>rId18 >>>media/pic1
//
for (Map<String, String> picMap : picList) {
if (target.endsWith(picMap.get("name"))) {
picMap.put("rId", id);
}
}
}
}
dataMap.put("modelList", picList);//覆盖原来的picList;
//================================获取 document.xml 输入流================================
ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlDocument, templatePath);
//================================获取 document.xml 输入流================================
// System.out.println("base_path_template+separator+docxTemplate===="+base_path_template+separator+docxTemplate);
File docxFile = new File(realTemplatePath + separator + docxTemplateFile);
if (!docxFile.exists()) {
docxFile.createNewFile();
}
ZipFile zipFile = new ZipFile(docxFile);
Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
File tempPath = new File(outputPath);
//如果输出目标文件夹不存在,则创建
if (!tempPath.exists()) {
tempPath.mkdirs();
}
ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(outputPath + outputFileName));
//------------------覆盖文档------------------
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 if ("word/header2.xml".equals(next.getName())){
//写入填充数据后的页眉信息
if (header2Input != null) {
while ((len = header2Input.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
header2Input.close();
}
}else if ("word/header3.xml".equals(next.getName())){
//写入填充数据后的页眉信息
if (header3Input != null) {
while ((len = header3Input.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
header3Input.close();
}
}
else {
while ((len = is.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
is.close();
}
}
}
//------------------覆盖文档------------------
//------------------写入新图片------------------
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();
}
}
//------------------写入新图片------------------
zipout.close();
} catch (Exception e) {
e.printStackTrace();
throw new Exception("生成docx文件失败!");
}
}
/**
* 删除文件
*
* @param listFiles
*/
public static void delFiles(List<String> listFiles) {
try {
if (listFiles != null && !listFiles.isEmpty()) {
for (String file_temp_path : listFiles) {
File file_temp = new File(file_temp_path);
if (file_temp.exists()) {
file_temp.delete();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
URL basePath = WordUtils.class.getClassLoader().getResource("");
// System.out.println("basePath.getPath() ==> " + basePath.getPath());
String picPath = basePath.getPath() + separator + "templates" + separator;
Map<String, Object> dataMap = new HashMap<>();
// 页眉
dataMap.put("ymdhis", DateUtils.getCurrentTime_yyyyMMddHHmmss());
// 水印
dataMap.put("waterPic", "水印模板");
// 图片类型
List<String> picTypes = new ArrayList<>();
picTypes.add("jpg");
dataMap.put("mdlTypes", picTypes);
// 文档标题
dataMap.put("title", "文档的标题");
dataMap.put("reportUnit", "文档的单位 ");
dataMap.put("reportTypeDate", "文档的报告周期");
// 模块内容列表
List<Map<String, String>> picList = new ArrayList<>();
Map<String, String> picMap = new HashMap<>();
// 要按顺序
picMap.put("path", picPath + "pic1.jpg");
picMap.put("name", "pic1.jpg");
picMap.put("modelTitle", "模块1标题");
picMap.put("modelDataSource", "模块1来源:美团");
picMap.put("modelShowContent", "模块1内容sasasaaaaaaaaaa");
picList.add(picMap);
picMap = new HashMap<>();
picMap.put("path", picPath + "pic2.jpg");
picMap.put("name", "pic2.jpg");
picMap.put("modelTitle", "模块2标题");
picMap.put("modelDataSource", "模块2来源:美团");
picMap.put("modelShowContent", "模块2内容sasasaaaaaaaaaa");
picList.add(picMap);
picMap = new HashMap<>();
picMap.put("path", picPath + "pic3.jpg");
picMap.put("name", "pic3.jpg");
picMap.put("modelTitle", "模块3标题");
picMap.put("modelDataSource", "模块3来源:美团");
picMap.put("modelShowContent", "模块3内容sasasaaaaaaaaaa");
picList.add(picMap);
dataMap.put("modelList", picList);
String timeStr = DateUtils.getCurrentTime_yyyyMMddHHmmssSSS();
String docxTemplateFile = "docxTemplates.docx";
//带水印的模板
String docxTemplatesWithWaterPic = "docxTemplatesWithWaterPic.docx";
String xmlDocument = "document.xml";
String xmlDocumentXmlRels = "document.xml.rels";
String xmlContentTypes = "[Content_Types].xml";
String xmlHeader = "header1.xml";//可以用来修改页眉的一些信息
String xmlHeader2 = "header2.xml";//可以用来修改页眉的一些信息
String xmlHeader3 = "header3.xml";//可以用来修改页眉的一些信息
String templatePath = separator + "templates" + separator;
String outputFileTempPath = "temp" + separator + timeStr + separator;
String outputFileName = timeStr + "."+suffix_docx;
// String outputFileName = timeStr + "."+suffix_doc;
/*
* @param dataMap 参数数据
* @param docxTemplateFile docx模主板名称
* @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
* */
try {
//不带水印
// createDocx(dataMap, docxTemplateFile, xmlDocument, xmlDocumentXmlRels, xmlContentTypes,
// xmlHeader, templatePath, outputFileTempPath, outputFileName);
//水印
createDocx(dataMap, docxTemplatesWithWaterPic, xmlDocument, xmlDocumentXmlRels, xmlContentTypes,
xmlHeader,xmlHeader2,xmlHeader3, templatePath, outputFileTempPath, outputFileName);
// String xmlDocumentXmlRelsComment = FreeMarkUtils.getFreemarkerContent(dataMap,xmlDocumentXmlRels,separator + "templates" );
// System.out.println(xmlDocumentXmlRelsComment);
} catch (Exception e) {
e.printStackTrace();
}
}
}
目录和我一样,本地运行就可以了
具体代码
https://gitee.com/zc0709/JavaUtilsProject
主要工具类
本文介绍了一种使用DOM4J和FreeMarker将包含文本和图表的数据导出为DOCX格式的方法,详细阐述了如何通过修改模板文件和利用Zip格式特性生成带有水印的Word文档。
611

被折叠的 条评论
为什么被折叠?



