根据word模板生成word(采用的是freemarker)

前言:

1. 因为项目需求,需要将业务数据导出word。所以想到了用word模板,然后用数据渲染。

2. 既然要做word模板引擎,自然需要选择一种引擎。刚开始我选用的是Poi-tl,但是很可惜,项目用的poi版本过低,要用的话,需要改很多地方。所以舍弃了,不过感觉Poi-tl比较方便。因为模板是docx文档,不存在我下面用freeMarker的问题。

3. Poi-tl 链接:https://github.com/Sayi/poi-tl很不错的word模板引擎,感兴趣的可以去试试。也许以后会写关于poi-tl的文章。

准备工作:

1. 第一步先下载依赖,我用的是maven,所以只需要再pom文件添加依赖即可。

2. 第二步创建特殊字符过滤类此类的作用是解决生成的word含有特殊字符,从而打不开。因为项目是用户输入的数据,中间存在html转义,一旦转义后,将生成的数据渲染到word文档中就会打不开。注:只要保证生成word没有转义字符,此类可以不要

/**
 * 导出word的时候过滤并替换特殊字符
 *
 * @author hhs
 * @version 2018-12-05 17:16
 */
public  class WordReplaceUtils {
    /**
     * 将map中的所有value值含有特殊字符过滤掉
     * @param map 渲染到模板的数据
     * @throws IllegalAccessException
     */
    public static void Wordreplace(HashMap<Object,Object> map) throws IllegalAccessException {
        //先获取所有的key值
        for (Object o : map.keySet()) {
            //根据key值获取value值
            Object value = map.get(o);
            //如果value的属性是 String类型,就直接将特殊字符替换掉
            if(value instanceof String){
                //先将中文引号替换成英文引号
                String replace = ((String) value).replace("&ldquo;","\"").replace("&rdquo;","\"");//替换中文引号符号
                //再替换其他特殊字符
                replace =  replace.replaceAll("&[a-zA-Z]+;", " ");//替换所有&开头;结尾的字符
                //重新设置value值,此处value值含有特殊字符的都已经过滤掉了。
                map.put(o,replace);
            }else if(value instanceof User ){
                //因为用到了自定义类,所以value值可能是自定义类
                Class<?> aClass = value.getClass();//先获取自定义类的class文件
                Field[] declaredFields = aClass.getDeclaredFields();//再根据class获取所有属性字段
                //遍历所有属性字段
                for (Field declaredField: declaredFields) {
                    //设置可以访问,如果是私有的属性字段,必须加这一段
                    declaredField.setAccessible(true);
                    //根据自定义对象获取该属性字段的值
                    Object o1 = declaredField.get(value);
                    //如果属性字段值为String,就替换后,再赋值过去
                    if (o1 instanceof String) {
                        //此处和上面一样
                        String replace = ((String) o1).replace("&ldquo;", "\"").replace("&rdquo;", "\"");//替换中文引号符号
                        replace =  replace.replaceAll("&[a-zA-Z]+;", " ");//替换所有&开头;结尾的字符
                        declaredField.set(value, replace);//重新将属性值赋给该自定义对象,map存储的value就会变了。如果不理解,可以看HashMap存储原理
                    }
                }
            }
        }
    }
    //测试正则是否对,用于匹配特殊字符,然后替换掉
    //特殊字符为&开头;结尾
    public static void main(String[] args){
        String a ="&mmmm;211221&rmod;&zmmm;232322&rmk;2232adad";
        String s = a.replaceAll("&[a-zA-Z]+;", " ");
        System.out.println(s);
    }
}

 3. 创建通用生成word类(重要

/**
 * @author hhs
 * @version 2018-12-03 13:14
 */
public class WordGenerator {
    private static Configuration configuration = null;
    private static String url ="D:\\template";//模板我们放在D盘下面的template文件夹下
    //进行freeMarker渲染前的设置,只用设置一次,所以写到了静态代码块中。
    static {
        configuration = new Configuration();
        try {
            //设置模板加载基础路径,请根据实际需求设置模板加载基础路径
            configuration.setDirectoryForTemplateLoading(new File(url));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //设置渲染字符集
        configuration.setDefaultEncoding("UTF-8");
        //设置模板异常处理
        configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);


    }
    //可以忽略,没有什么用,只是防止被实例化
    private WordGenerator() {
        throw new AssertionError();
    }

    /**
     * 渲染doc核心方法
     * @param dataMap 渲染数据
     * @param xmlName 要用的模板,要保证模板加载基础路径+xmlName的路径能够找到该模板
     * @return 渲染后的文件
     */
    public static File createDoc(Map<?, ?> dataMap, String xmlName)  {
        //渲染后的临时文件名,因为我们要提供浏览器下载,所以该文件当浏览器下载完就会删掉
        String name = "temp" + (int) (Math.random() * 100000) + ".doc";//保证不重名
        File f = new File(name);//得到临时文件
        Template t = null;//定义模板
        try {
            //替换特殊字符,防止生成的word打不开,如果不打算写这个方法,可以去掉
            WordReplaceUtils.Wordreplace((HashMap<Object, Object>) dataMap);
            t = configuration.getTemplate(xmlName);//得到模板
            // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
            Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
            t.process(dataMap, w);//渲染模板
            w.close();//关闭流
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
        return f;
    }

}



​

4. 准备模板 (请根据自己的实际情况创建模板)

  • 先创建一个word文档另保存成xml,内容如图。

  • 再用文本方式打开,ctrl+F搜索$,如图所示:

  • 再删除无用的标签,是${变量}连起来,并替换成${user.loginName!""}类似的结构(因为如果中间有标签,freeMarker当成变量解析。!"" 代表如果变量为null,就输出空的字符串,如果不加这个,渲染的时候会报错。!"" 一定要是英文),修改之后的结果如图:

开始

1. 准备工作完成,接下来的是一个例子,请根据自己的实际情况使用。

/**
 * 生成word
 *
 * @author hhs
 * @version 2018-12-07 12:40
 */
public class CreateDoc {
    @Test
    public void test(){
        HashMap<Object, Object> map = new HashMap<>();
        User user = getUser();//得到数据,一般这步是从数据库获取的
        setMap(map,user);//将数据放到map中
        String filepath = "userTemplate.xml";//模板文件,位于D://template目录下。请看WordGenerator类的设置。
        File doc = WordGenerator.createDoc(map, filepath);//渲染文件,并生成文件
        //下载文件,省略。
        System.out.println(doc.getAbsolutePath());//打印文件的绝对路径
    }
    /**
     * 获取数据
     * @return
     */
    private User getUser() {
        User user = new User();
        user.setLoginName("张三");
        user.setEmail("xxxxxx@qq.com");
        user.setRemarks("这个是“”中文符号");
        return user;
    }
    /**
     * 将数据填充到map中
     * 此处可以对数据进行处理(业务方面)
     * @param map
     * @param user
     */
    private void setMap(HashMap<Object, Object> map, User user) {
        map.put("user",user);
    }
}

2. 查看生成的文件(此处为了证明生成文件是否正确)

问题总结 

  1. 模板一定要把${}合并在一起,不然freeMarker解析不了,合并的时候,可以去ctrl+H删除无用的标签。
  2. WordReplaceUtils类一定要根据需求过滤不需要的转义字符,当文档打不开的时候,会提醒你第几行第几列实体出错,可以先以文本形式打开,看看有什么不同。然后过滤或转义相关字符。
  3. User是一个自定义类。
  4. 每通过word调整模板的时候,保存后一定要通过第一步保证${}是合并在一起的。(PS:我改了好几次模板,看的眼都花了。)
  5. 暂时能想到的问题就这么多。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值