导出word文档生成docx格式 添加水印_freemarker模板可以添加水印吗-程序员宅基地

技术标签: word加水印  工具  

为了导出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

主要工具类

https://gitee.com/zc0709/JavaUtilsProject/blob/master/src/main/java/com/sl/utils/office/word/WordUtils.java

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36068521/article/details/103670890

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue