FreeMarker导出Word(带图片、解决图片变形问题)

喜你入骨 提交于 2019-11-29 10:40:54

思想说明:
        本文内容的核心:使用FreeMarker的模板引擎技术,导出word。


总体步骤:

第一步:创建一个word模板,里面的对应位置使用FreeMarker的占位符表示。

注:为了导出的word兼容2003版本,我们创建的word模板,最好是doc后缀的。

注:本人用的是wps,office应该也是一样的(这个没试过)。

第二步:将该word,另存为xml文件。

提示:也可以第二步时先用一个特别的字符占位,然后在第三步时,再在xml中将对应的字符替换为FreeMarker能识别
           的占位符。

第三步:打开该xml文件,检查占位符是否"变形"

提示:本人是以安装了indent XML插件的sublime打开的。

注:有时,另存为xml时,占位符可能会变形(如:“${age}”变为“${ag</w:t></w:r></w:p></w:tc>e}”),这就会导致FreeMarker
       不能识别,那么就需要我们手动调整或者重复第二步重新生成,直到所有的占位符都正确。

第四步:创建一个项目,并引入FreeMarker的jar包。

<!--FreeMarker -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

注:本人创建的是maven项目(SpringBoot),直接在pom.xml中引入FreeMarker的依赖即可。

第五步:将xml文件,修改后缀为(freemarker模板的).ftl,并放入项目classpath下
               的templates目录下。

第六步:编写导出word工具类

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import sun.misc.BASE64Encoder;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * freemarker + word模板 导出word
 *
 * @author JustryDeng
 * @date 2018/11/14 17:41
 */
public class FreemarkeExportrWordUtil {

    /** 默FreeMarker配置实例 */
    private static final Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);

    /** 默认采用UTF-8编码 */
    private static final String ENCODING = "UTF-8";

    /** buffer */
    private static final int BUFFER_SIZE = 1024;

    /**
     * 从指定freemarker模板所在文件夹
     *
     * “/”            对应 classpath
     * “/templates/”  对应 classpath下的templates/
     */
    private static final String DEFAULT_POSITION = "/";

    static {
        configuration.setDefaultEncoding(ENCODING);
    }

    /**
     * 导出excel
     *
     * @param templateFileName
     *         模板文件名(含后缀,如:abc.ftl)
     * @param resultFileAllPathName
     *         结果文件全路径文件名 (如: C:/Users/result.doc  再如: C:/Users/result.docx)
     * @param dataObject
     *         与模板中的占位符 对应的 数据信息(一般为:一个专门创建的对象, 或者是Map)
     * @return 生成的word文件
     * @throws IOException
     * @throws TemplateException
     * @date 2018/11/16 10:52
     */
    public static File doExport(String templateFileName, String resultFileAllPathName, Object dataObject)
                                throws IOException, TemplateException {
        return doExport(templateFileName, DEFAULT_POSITION, resultFileAllPathName, dataObject);
    }

    /**
     * 导出excel
     *
     * @param templateFileName
     *         模板文件名(含后缀,如:abc.ftl)
     * @param templateFileDir
     *         模板文件所在位置名(如: "/" 代表 classpath)
     * @param resultFileAllPathName
     *         结果文件全路径文件名 (如: C:/Users/result.doc  再如: C:/Users/result.docx)
     * @param dataObject
     *         与模板中的占位符 对应的 数据信息(一般为:一个专门创建的对象, 或者是Map)
     * @return 生成的word文件
     * @throws IOException
     * @throws TemplateException
     * @date 2018/11/16 10:52
     */
    public static File doExport(String templateFileName, String templateFileDir,
                                String resultFileAllPathName, Object dataObject)
                                throws IOException, TemplateException {

        // 指定模板文件所在  位置
        configuration.setClassForTemplateLoading(FreemarkeExportrWordUtil.class, templateFileDir);

        // 根据模板文件、编码;获取Template实例
        Template template = configuration.getTemplate(templateFileName, ENCODING);

        File resultFile = new File(resultFileAllPathName);
        // 判断要生成的word文件所在目录是否存在,不存在则创建
        if (!resultFile.getParentFile().exists()) {
            boolean result = resultFile.getParentFile().mkdirs();
        }
        // 写出文件
        try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(resultFile));
             Writer writer = new BufferedWriter(osw, BUFFER_SIZE)) {
            template.process(dataObject, writer);
        }
        return resultFile;
    }

    /**
     * 获取图片对应的base64码
     *
     * @param imgFile
     *         图片
     * @return 图片对应的base64码
     * @throws IOException
     * @date 2018/11/16 17:05
     */
    public static String getImageBase64String(File imgFile) throws IOException {
        InputStream inputStream = new FileInputStream(imgFile);
        byte[] data = new byte[inputStream.available()];
        int totalNumberBytes = inputStream.read(data);
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(data);
    }

}

第七步:使用工具类导出word即可


使用测试:

测试示例一:基本测试

测试主函数为:

public static void main(String[] args) {
        // 模板文件名
        String templateFileName = "exportWordDemoTwo.ftl";
        // 模板文件所在位置
        String templateFileDir = "/templates/";
        // 要生成的文件 全路径文件名
        String fileName = "C:/Users/JustryDeng/Desktop/result.doc";

        // 组装FreeMarker占位符对应的数据
        Map<String, Object> dataMap = new HashMap<>(8);
        dataMap.put("year", "2018");
        dataMap.put("month", 11);
        dataMap.put("day", 16);
        dataMap.put("name", "JustryDeng");
        dataMap.put("age", 24);
        dataMap.put("handsomeValue", 100);
        dataMap.put("isSingle", "是");
        try {
            FreemarkeExportrWordUtil.doExport(templateFileName, templateFileDir, fileName, dataMap);
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
    }

执行主函数后,在指定位置就生成了对应的文件:

打开该文件:

测试示例二:集合数据测试

我们回到第二步、第三步;在xml中添加对应的freemarker标签:

其他的不变。这次将该xml后缀名改为ftl,这次放入classpath下(这样做纯粹是为了使用一下工具类中的另一个方法):

测试主函数为:

public static void main(String[] args) {
        // 模板文件名
        String templateFileName = "exportWordDemoTwo.ftl";
        // 要生成的文件 全路径文件名
        String fileName = "C:/Users/JustryDeng/Desktop/result123.doc";

        // 组装FreeMarker占位符对应的数据
        // 组装FreeMarker占位符对应的数据
        Map<String, Object> dataMap = new HashMap<>(8);
        dataMap.put("year", "2018");
        dataMap.put("month", 11);
        dataMap.put("day", 16);
        List<Map<String, Object>> list = new ArrayList<>(8);
        dataMap.put("personList", list);
        for (int i = 0; i < 10; i++) {
            Map<String, Object> tempMap = new HashMap<>(8);
            tempMap.put("name", "JustryDeng");
            tempMap.put("age", 24);
            tempMap.put("handsomeValue", 90 + i);
            tempMap.put("isSingle", "是");
            list.add(tempMap);
        }
        try {
            FreemarkeExportrWordUtil.doExport(templateFileName, fileName, dataMap);
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
    }

执行主函数后,在指定位置就生成了对应的文件:

打开该文件:

测试示例三:导出图片测试

同样的,先在word中放一张图片:

然后在第三步时,将xml中该图片对应的base64码替换为freemarker对应的占位符:

替换结果如图所示:

其他的不变。将该xml后缀名改为ftl,并将模板文件放入classpath下:

测试主函数为:

public static void main(String[] args) {
        // 模板文件名
        String templateFileName = "exportWordDemoThree.ftl";

        // 要生成的文件 全路径文件名
        String fileName = "C:/Users/JustryDeng/Desktop/resultImage.doc";

        // 组装FreeMarker占位符对应的数据
        Map<String, Object> dataMap = new HashMap<>(8);
        dataMap.put("year", "2018");
        dataMap.put("month", 11);
        dataMap.put("day", 16);
        dataMap.put("name", "JustryDeng");
        dataMap.put("age", 24);
        dataMap.put("handsomeValue", 100);
        dataMap.put("isSingle", "是");
        try {
            File imageFile = new File("C:/Users/JustryDeng/Desktop/66.png");
            dataMap.put("myImage", FreemarkeExportrWordUtil.getImageBase64String(imageFile));
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            FreemarkeExportrWordUtil.doExport(templateFileName, fileName, dataMap);
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
    }

执行主函数后,在指定位置就生成了对应的文件:

打开该文件:

注:实际上,导出的word里面的图片,宽高还是原来模板图片的大小;也就是说:这可能导致导出的word里面的图片出
     现变形。


解决图片变形:

指定图片的宽高:

在xml中对应设置宽高的位置,使用freemarker占位符:

在程序中指定图片的宽高:

public static void main(String[] args) {
        // 模板文件名
        String templateFileName = "exportWordDemoThree.ftl";

        // 要生成的文件 全路径文件名
        String fileName = "C:/Users/JustryDeng/Desktop/resultImage.doc";

        // 组装FreeMarker占位符对应的数据
        Map<String, Object> dataMap = new HashMap<>(8);
        dataMap.put("year", "2018");
        dataMap.put("month", 11);
        dataMap.put("day", 16);
        dataMap.put("name", "JustryDeng");
        dataMap.put("age", 24);
        dataMap.put("handsomeValue", 100);
        dataMap.put("isSingle", "是");
        try {
            File imageFile = new File("C:/Users/JustryDeng/Desktop/66.png");
            BufferedImage bufferedImage = ImageIO.read(imageFile);
            // 单位为 px
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            // 设置图片的大小
            dataMap.put("width", width + "px");
            dataMap.put("height", height + "px");
            dataMap.put("myImage", FreemarkeExportrWordUtil.getImageBase64String(imageFile));
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            FreemarkeExportrWordUtil.doExport(templateFileName, fileName, dataMap);
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
    }

 

提示:由于freemarker模板走的其实是XML,所以如果想输出类似于“>“、“<”这样的XML敏感的字符的话,
            需要将“<”写为“&lt;”、“>”写为“&gt;”,如:

 

^_^ 如有不当之处,欢迎指正
^_^ 本文已经被收录进《程序员成长笔记(三)》,笔者JustryDeng

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!