为了导出docx格式看了等多文档,最后做个总结依赖包用到dom4j和freemarker,最为方便。
<!-- /artifact/freemarker/freemarker --><dependency><groupId>freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.9</version></dependency><!-- /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 .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 docxTemplateFiledocx模主板名称* @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/0914051811/* @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();// 获取根节点下的子节点headList<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 docxTemplateFiledocx模主板名称* @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/0914051811/* @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();}}}
目录和我一样,本地运行就可以了
具体代码
/zc0709/JavaUtilsProject
主要工具类
/zc0709/JavaUtilsProject/blob/master/src/main/java/com/sl/utils/office/word/WordUtils.java