zoukankan      html  css  js  c++  java
  • java生成word文档

    java生成word文档

    最近得到一个需求:按用户提供的模板生成分析报告,并让用户可以在网页上导出。这个功能以前没做过,但是好像听说过freemarker。于是乎,开始了我的百度之旅。

    一、word文档的本质

    我也是最近才知道,word文档的本质原来是一个压缩文件。不信你看,将.docx文件修改文件后缀为.zip

    然后解压缩得到了这些文件,这些就是组成word文档的所有文件。其中word文件夹下是主要内容

    ​其中,document.xml中是关于文档内容的设置,相当于网页里面的html文件一样。_rels文件夹下的document.xml.rels文件是图片配置信息。media文件夹下是文档中所有图片的文件,其他的应该是类似于网页里面的CSS文件,设置样式的。所以document.xml就是我们要修改的了。这样的操作就相当于网页已经编写好了,只差从后台传送数据到前端展示了。

    二、创建freemarker模板

    freemarker是一个模板引擎,百度是这样介绍的:

    FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件配置文件源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

    ​说白了就是跟前端页面的变量绑定是一样的,用过vuejs的都知道,前端使用“{{name}}”双括号括起来的变量可以通过传值改变页面上数据。freemarker也是这样,通过在document.xml中使用“${name}”dollar符大括号括起来的变量也可以通过传值改变模板文件内容。清楚了这点就好办了。

    将document.xml文件放到IDEA项目中的templates文件夹下,然后按Ctrl+Alt+L键格式化xml内容,将需要动态修改的地方用“${}”括起来,如下

    有的时候变量会被拆分成两个,就要麻烦点把两个中间的多余部分全都删掉,然后在用符号括起来。这点相信大家都能理解。

    以上模板创建就结束了,是不是很简单。

    三、代码实现

    模板搞定了,怎么根据模板生成文档呢?关键步骤来了。

    1.首先我们要将模板中的变量赋值,生成新的文件。

    2.将生成的文件写入压缩文件。上面已经说了,word文档本质就是压缩文件。

    3.将.docx文档的其他内容也写入压缩文件。

    4.将压缩文件写入word文档,这就是最后生成的文档。

    导入依赖:

    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.28</version>
    </dependency>
    

    主要用到下面的工具类:

    
    public class FreeMarkUtils {
        private static Logger logger = LoggerFactory.getLogger(FreeMarkUtils.class);
    
        public static Configuration getConfiguration(){
            //创建配置实例
            Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
            //设置编码
            configuration.setDefaultEncoding("utf-8");
            configuration.setClassForTemplateLoading(FreeMarkUtils.class, "/templates");//此处设置模板存放文件夹名称
            return configuration;
        }
    
        /**
         * 获取模板字符串输入流
         * @param dataMap   参数,键为绑定变量名,值为变量值
         * @param templateName  模板名称
         * @return
         */
        public static ByteArrayInputStream getFreemarkerContentInputStream(Map dataMap, String templateName) {
            ByteArrayInputStream in = null;
            try {
                //获取模板
                Template template = getConfiguration().getTemplate(templateName);
                StringWriter swriter = new StringWriter();
                //生成文件
                template.process(dataMap, swriter);
                in = new ByteArrayInputStream(swriter.toString().getBytes("utf-8"));//这里一定要设置utf-8编码 否则导出的word中中文会是乱码
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("模板生成错误!");
            }
            return in;
        }
        //outputStream 输出流可以自己定义 浏览器或者文件输出流
        public static void createDocx(Map dataMap, OutputStream outputStream) {
            ZipOutputStream zipout = null;
            try {
                //内容模板,传值生成新的文件输入流documentInput
                ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, "document.xml");
                //最初设计的模板,原word文件生成File对象
                File docxFile = new File(WordUtils.class.getClassLoader().getResource("templates/demo.docx").getPath());//模板文件名称
                if (!docxFile.exists()) {
                    docxFile.createNewFile();
                }
                ZipFile zipFile = new ZipFile(docxFile);//获取原word文件的zip文件对象,相当于解压缩了word文件
                Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();//获取压缩文件内部所有内容
                zipout = new ZipOutputStream(outputStream);
                //开始覆盖文档------------------
                int len = -1;
                byte[] buffer = new byte[1024];
                while (zipEntrys.hasMoreElements()) {//遍历zip文件内容
                    ZipEntry next = zipEntrys.nextElement();
                    InputStream is = zipFile.getInputStream(next);
                    if (next.toString().indexOf("media") < 0) {
                        zipout.putNextEntry(new ZipEntry(next.getName()));//这步相当于创建了个文件,下面是将流写入这个文件
                        if ("word/document.xml".equals(next.getName())) {//如果是word/document.xml由我们输入
                            if (documentInput != null) {
                                while ((len = documentInput.read(buffer)) != -1) {
                                    zipout.write(buffer, 0, len);
                                }
                                documentInput.close();
                            }
                        } else {
                            while ((len = is.read(buffer)) != -1) {
                                zipout.write(buffer, 0, len);
                            }
                            is.close();
                        }
                    }else{//这里设置图片信息,针对要显示的图片
                        zipout.putNextEntry(new ZipEntry(next.getName()));
                            while ((len = is.read(buffer)) != -1) {
                                zipout.write(buffer, 0, len);
                            }
                            is.close();
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("word导出失败:"+e.getStackTrace());
            }finally {
                if(zipout!=null){
                    try {
                        zipout.close();
                    } catch (IOException e) {
                        logger.error("io异常");
                    }
                }
                if(outputStream!=null){
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        logger.error("io异常");
                    }
                }
            }
        }
    }
    

    四、插入图片

    ​通过传值已经可以基本完成生成文档的功能了,但是用户还要求要在文档中生成统计分析图。知道文档本质的我马上想出了办法,但是这个就稍微有点麻烦了。刚刚说了,media文件夹下存放的是文档中所有的图片。我可以在word文档要生成统计图的地方先用图片占好位置,调好大小。然后解压后看这个图片在media文件夹中叫什么名字。最后在代码生成的时候,跳过原文件图片的写入替换成我生成的图片就可以了。核心代码如下:

      while (zipEntrys.hasMoreElements()) {
                    ZipEntry next = zipEntrys.nextElement();
                    InputStream is = zipFile.getInputStream(next);
                    if (next.toString().indexOf("media") < 0) {
                       ...省略
                    }else{//这里设置图片信息,针对要显示的图片
                        zipout.putNextEntry(new ZipEntry(next.getName()));
                        if(next.getName().indexOf("image1.png")>0){//例如要用一张图去替换模板中的image1.png
                            if(dataMap.get("image1")!=null){
                                byte[] bys = Base64Util.decode(dataMap.get("image1").toString());
                                zipout.write(bys,0,bys.length);
                            }else{
                                while ((len = is.read(buffer)) != -1) {
                                    zipout.write(buffer, 0, len);
                                }
                                is.close();
                            }
                        }
                    }
                }
    

    注意:这个Base64Util是一个将图片文件转化为base64编码字符串和将base64编码字符串转换为字节数组的工具类。这里涉及到图片文件的本质,图片的本质是一个二进制文件,二进制文件可以转换成字节数组,而字节数组又可以和字符串互相转换。这个知道就好,这里我把生成的图片的字符串存入了集合中,写入文档的时候将字节数组写入。这样原图片就被替换成了生成的图片了。这里直接将新的图片文件写入也是一样的效果。

  • 相关阅读:
    SpringMVC之文件上传
    Spring之jdbc【继承JdbcDaoSupport】
    Spring中jdbcTemplate的用户实例
    SpringMVC之数据存储
    SpringMVC的日期转换
    SpringMVC配置解决中文乱码的过滤器
    【对数据库操作的封装成工具类之jdbc】
    实现用户注册与登入功能的案例
    【反射之Field】获取字段
    Can't get WebApplicationContext object from ContextRegistry.GetContext(): Resource handler for the 'web' protocol is not defined
  • 原文地址:https://www.cnblogs.com/conti/p/14281473.html
Copyright © 2011-2022 走看看