Java 中利用 Freemark 生成 Word 文档

FreeMarker是一款模板引擎:即一种基于模板、用来生成输出文本(任何来自于HTML格式的文本用来自动生成源代码)的通用工具。 使用MVC模式的动态页面的设计构思使得你可以将前端设计师(编写HTML页面的人员)从程序员中分离出来。本文为使用 freemarker 来生成 word 文档的示例。


工具

  • Word2003 以上版本或者 wps2016 的版本(即可以支持文档另存为xml格式的版本)
  • jar包:freemark.jar

第一步:数据准备

  • 一张图片:1.jpg
  • 一个word文档。内容如下:
word文档

第二步:制作模板

  1. 把word文档另存为.xml格式,我起名称为template.xml
  2. 利用Notepad++或者sublime text这样的工具打开template.xml文件。
  3. 利用Ctrl+F搜索word中你写的信息,如:小明、男...等关键字找到template.xml中对应的字,用${name}${sex}等值代替,${}中的值为代码中要赋值的变量。
  4. 搜索<pkg:binarydata></pkg:binarydata>会发现这俩标签之间是一堆特别特别长的字符。像是这样:

<pkg:binarydata>

把中间的字符替换成${image},如图:

图片替换

4.把替换好的template.xml文件后缀名改为.ftl。至此,模板制作成功。

第三步:编写代码

基于 SpringMVC ,简单写了个 Demo。

页面 index.jsp

<body>
    <center>
        <div style="padding: 3px 2px; border-bottom: 1px solid #ccc">填写相关数据进行导出</div>
        <form name="form" action="" method="post">
            <table>
                <tr>
                    <td>姓名:</td>
                    <td><input name="name" type="text"></input></td>
                </tr>
                <tr>
                    <td>性别:</td>
                    <td><input id="man" name="sex" type="radio" value='1' checked />男
                        <input id="woman" name="sex" type="radio" value='0' />女</td>
                </tr>
                <tr>
                    <td>生日:</td>
                    <td><input name="birthday" type="text"></input></td>
                </tr>
                <tr>
                    <td>电话:</td>
                    <td><input name="telphone" type="text"></input></td>
                </tr>
                <tr>
                    <td>邮箱:</td>
                    <td><input name="email" type="text"></input></td>
                </tr>
                <tr>
                    <td>地址:</td>
                    <td><input name="address" type="text"></input></td>
                </tr>
                <tr>
                    <td>头像路径:</td>
                    <td><input id="photo" name="photo" type="text" /></td>
                </tr>
            </table>
            <br>
            <table>
                <tr>
                    <input type="button" value="导出Word" onclick="exportData('word')">
                </tr>
            </table>
        </form>
    </center>
</body>
<script type="text/javascript">
    function exportData(obj) {
        if (obj == "word") {
            form.action = "${pageContext.request.contextPath}/exportWord.action";
            form.submit();
        }
    }
    
    function showPreview(obj){
        var str = obj.value;
        document.getElementById("previewImg").innerHTML = "![]( + str + )";
    } 
</script>

工具类 ExportWorldUtil.java

package com.export.util;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Map;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import sun.misc.BASE64Encoder;

/**
 * 数据导出World工具类
 * 
 * @author qixiantong
 *
 */
public class ExportWorldUtil {
    private Configuration configuration;

    public void configure(String templatePath) {
        configuration = new Configuration();
        configuration.setDefaultEncoding("utf-8");
        configuration.setClassForTemplateLoading(this.getClass(), templatePath);
    }

    public void create(String templatePath, String templateName, String filePath, String fileName,
            Map<String, Object> map) {
        this.configure(templatePath);
        Template template = null;
        try {
            // 读取freemarker的导出模板
            template = configuration.getTemplate(templateName);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 创建一个file对象
        File outFile = new File(filePath + fileName);
        // 写入字符流的抽象类
        Writer out = null;
        try {
            // 创建一个使用默认大小输出缓冲区的缓冲字符输出流
            // OutputStreamWriter 字节-字符转换流
            // FileOutputStream 文件输出流
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        if (map != null) {
            try {
                // 执行模板,使用提供的数据模型
                template.process(map, out);
                out.close();
            } catch (TemplateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 由于word中图片为base64编码, 故将图片转化为base64编码后的字符串
     * @author qixiantong
     * @return String
     */
    public String getImageStr(String imgFile) {
        // String imgFile = new File(this.getClass().getResource("/").getPath())
        // + "/com/export/doc/1.jpg";
        InputStream in = null;
        byte[] data = null;
        try {
            // 创建照片的字节输入流
            in = new FileInputStream(imgFile);
            // 创建一个长度为照片总大小的内存空间
            data = new byte[in.available()];
            // 从输入流中读取文件大小的字节,并将其存储在缓冲区数组 data中
            in.read(data);
            // 关闭输入流
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        BASE64Encoder encoder = new BASE64Encoder();
        // 进行base64加密
        return encoder.encode(data);
    }
}

控制器 ExportDataAction.java

    /**
     * 起始页面
     * 
     * @return
     */
    @RequestMapping("/index")
    public String login() {
        return "index";
    }
    /**
     * 导出到Word
     * 
     * @author zhang_cq
     * @return
     */
    @RequestMapping("/exportWord")
    public String exportWord(HttpServletRequest request, Person person) {
        try {
            ExportWorldUtil util = new ExportWorldUtil();
            String templatePath = "../doc/"; // 模板路径
            String templateName = "template.ftl"; // 模板名称
            String filePath = "d:/";
            String fileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".doc"; // 生成的World文档路径
            ExportWorldUtil ewu = new ExportWorldUtil();
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("name", person.getName());
            String sex = person.getSex();
            map.put("sex", "1".equals(sex) ? "男" : "女");
            String imagePath = person.getPhoto();
            //imagePath = imagePath.replace("\\", "/");
            map.put("image", util.getImageStr(imagePath));
            SimpleDateFormat bartDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Date birthday = person.getBirthday();
            if (birthday != null) {
                String birth = bartDateFormat.format(birthday);
                map.put("birthday", birth);
            }
            map.put("telphone", person.getTelphone());
            map.put("email", person.getEmail());
            map.put("address", person.getAddress());
            ewu.create(templatePath, templateName, filePath, fileName, map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "index";
    }   
    // 使用注解进行日期格式数据转换
    @InitBinder
    private void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
        // 指定日期类型及日期数据的格式
        // 日期类型要和student类的birthday一致
        binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
    }

至此,在 java 中 生成 word 文档的实例就做完了。想看完整代码,欢迎访问https://coding.net/u/zhang_cq/p/ExportData/git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,510评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 多年未见的你还好么……
    老友纪阅读 148评论 0 0
  • 关于痛苦,有一些生活中的案例: 比如你买的东西不够好,就和商家撕逼,结果最后不欢而散; 比如你被某人说了一句,然后...
    万能的船长阅读 371评论 0 2