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

智能推荐

git 使用场景 本地分支 关联 远程分支-程序员宅基地

文章浏览阅读796次,点赞8次,收藏7次。【代码】git 使用场景 本地分支 关联 远程分支。

【IDE 小程序】小程序控制台 不打印 console.log问题_微信小游戏 真机调试 无法打印任何log-程序员宅基地

文章浏览阅读1.2k次。全局搜索compress.drop_console(一般在config文件中),设置为false,再重新打开小程序即可。_微信小游戏 真机调试 无法打印任何log

整合ssm使用Vue实现前后端数据交互(详细版)_ssmvue前后端怎么连接-程序员宅基地

文章浏览阅读9.2k次,点赞19次,收藏83次。使用vue 实现前后端数据的交互创建vue工程创建ssm项目结果创建vue工程一.创建webpack骨架类型的vue项目格式参考如下:二.npm操作:安装vue-router:npm install vue-router --save-dev安装element-ui:npm i element-ui -S安装SAss加载器:npm install sass-loader node-sass --save-dev安装axios:npm install --save axios vu_ssmvue前后端怎么连接

windows安装kafka配置SASL-PLAIN安全认证_windows 安装 sasl-程序员宅基地

文章浏览阅读924次。confluent是平台化的工具,封装了kafka,让我们可以更方便的安装和使用监控kafka,作用类似于CDH对于Hadoop。confluent是由LinkedIn开发出的团队成员,基于这项技术创立了新公司ConfluentConfluent的产品也是围绕着Kafka做的。_windows 安装 sasl

云HIS住院业务模块常见问题及解决方案_his错误定义按领域划分设计方案-程序员宅基地

文章浏览阅读736次。基于云计算技术的B/S架构的医院管理系统(简称云HIS)_his错误定义按领域划分设计方案

2018 华为软件精英挑战赛-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏4次。和室友一起参加了2018 华为软件精英挑战赛,现在来写写参赛感受吧。很遗憾,都没能进复赛,所以只参加了初赛,江山赛区64强,离进复赛只有一点点,当时江山和西北赛区还流传着,如果在其他赛区就可以进了,都是后话。写这个纯粹记录研究生生活吧。1、初赛赛题由于租户对ECS实例(虚拟机,VM)请求的行为具有一定规律,可以通过对历史ECS实例请求的分析,预测到未来一段时间的ECS实例请求,然后对预...

随便推点

[Docker] docker network 常见模式及常用命令介绍_docker network命令-程序员宅基地

文章浏览阅读2.6k次。前言前端时间, 使用docker过程中, 又接触到需要docker network的相关知识. 之前的了解都是, 可以满足日常的使用即可, 本次, 借着机会仔细的了解了一下.当然, 对于网络知识的更加深入的理解可能没有精通网络的大佬玩的转.docker的网络类型 及相应构造据悉. docker 1.7和docker1.8 共有4种默认的网络类型, 其分别为: None/ Host/ Bridge(默认)/ Container.​通过docker network ls查看docker默认_docker network命令

修改Anaconda镜像源_修改anaconda的镜像源-程序员宅基地

文章浏览阅读824次,点赞12次,收藏10次。也可更换为如下国内常用源。_修改anaconda的镜像源

配置管理的内容_配置管理的六个活动-程序员宅基地

文章浏览阅读4.5k次。配置管理6个主要活动:制订配置管理计划、配置标识、配置控制、配置状态报告、配置审计、发布管理和交付配置管理的配置项:可以分为基线配置项和非基线配置项两类,例如,基线配置项可能包括所有的设计文档和源程序等;非基线配置项可能包括项目的各类计划和报告等。配置管理的权限:所有配置项的操作权限应由CMO严格管理,基本原则是∶基线配置项向开发人员开放读取的权限;非基线配置项向PM、CCB及相关人员开放配置管理配置项的状态:可分为"草稿""正式"和"修改"三种。配置项刚建立时,其状态为"草稿"。配置项通过评审_配置管理的六个活动

SystemVerilog 断言:立即断言_systemverilog立即断言-程序员宅基地

文章浏览阅读139次。在 SystemVerilog 中,我们可以使用两种类型的断言:立即断言(Immediate Assertion)和并发断言(Concurrent Assertion)。如果条件为假,则断言失败,仿真停止,并将错误信息输出到仿真日志中。在每个时钟周期之后,我们使用立即断言来验证计数器的值是否按预期递增。如果断言失败,则会输出相应的错误消息,并停止仿真。通过使用断言,我们可以在仿真过程中验证设计的正确性,提高设计的可靠性和调试效率。通过使用立即断言,我们可以在仿真运行时实时检查设计的正确性。_systemverilog立即断言

ssh长时间不操作便断开_SSH连接服务器时,长时间不操作就会断开的解决方案-程序员宅基地

文章浏览阅读610次。最近在配置服务器相关内容时候,不同的事情导致长时间不操作,页面就断开了连接,不能操作,只能关闭窗口,最后通过以下命令解决。SSH连接linux时,长时间不操作就断开的解决方案:1、修改/etc/ssh/sshd_config文件,如果找到 ClientAliveInterval 0和ClientAliveCountMax 3并将注释符号("#")去掉,将ClientAliveInterval对应的..._三个小时无人 ssh就会断联

图解WinHex使用入门_please use latin characters-程序员宅基地

文章浏览阅读2.7w次,点赞15次,收藏86次。一 Winhex和相关概念简介1 Winhex是在Windows下运行的十六进制编辑软件,此软件功能非常强大,有完善的分区管理功能和文件管理功能,能自动分析分区链和文件簇链,能对硬盘进行不同方式不同程度的备份,甚至克隆整个硬盘;它能够编辑任何一种文件类型的二进制内容(用十六进制显示)其磁盘编辑器可以编辑物理磁盘或逻辑磁盘的任意扇区,是手工恢复数据的首选工具软件。2 数据恢复概念_please use latin characters

推荐文章

热门文章

相关标签