Java根据word 模版生成 word文件,支持 循环多图片及表格多图片 支持按照标记去除表格第一行表头

7 篇文章 1 订阅
5 篇文章 0 订阅

模版文件

jar包

        <dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.12.1</version>
        </dependency>

可能会出现poi jar包冲突 如出现 将老版本的poi排掉就好 ,也可能出现 log4j 版本低 用下面这个就行

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.17.1</version>
        </dependency>

代码

package com.util;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.HyperlinkTextRenderData;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.Pictures;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import com.deepoove.poi.util.PoitlIOUtils;
import com.zjjw.platform.core.exception.BusinessRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.springframework.core.io.ClassPathResource;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * POI-TL 进行转换 支持多图片及循环增加表格
 * <p>
 * 使用方式:
 * 参数传入类型 Map<String,Object>
 * 模版 纯文本 为:
 * {{name}} 则map.put("name",params) name 是自己定义的名字 下面的都是自己定义名字,只是示例
 * <p>
 * 图片为:
 * {{@imgUrl}} 只要是 图片的 都需在参数前 加 @ 符号 这个只支持单张图片!!! map.put("imgUrl",params)
 * <p>
 * 循环表格:表格示例:
 * {{list}} 用户名称 | 年龄 | 图片
 * [user] | [age] | [?img1][@#this][/img1]
 * list 为表格定义配置 需在配置文件配置,已提供默认表格配置 和 自定义配置方法 如一个文档中有 多处 需要循环表格 {{list}} 不可重复
 * 示例:
 * String avatar = "http://deepoove.com/images/icecream.png,http://deepoove.com/images/icecream.png,http://deepoove.com/images/icecream.png";
 * List<PictureRenderData> imgList = Arrays.stream(avatar.split(",")).map(s -> Pictures.ofStream(getInputStream(s))
 * .size(50, 50).create()).collect(Collectors.toList());
 * List<Map<String, Object>> par = new ArrayList<>();
 * for (int i = 0; i <= 1; i++) {
 * Map<String, Object> map1 = new HashMap<>(16);
 * map1.put("user", "测试一下吧");
 * map1.put("age", "123");
 * map1.put("img1", imgList);
 * par.add(map1);
 * }
 * map.put("list", par);
 * 其中 getInputStream(s) 方法是 获取url文件流方法,这里也可填入文件流 或 Pictures. 按自己需求来
 * <p>
 * 空白处循环添加图片:
 * {{?imga}}
 * {{@#this}}
 * {{#table}}
 * {{/imga}}
 * <p>
 * {{#table}} 占位符
 * <p>
 * 放入map方式如上
 * <p>
 * 支持 删除表格的 第一行 也就是表头 removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
 *
 * @author: chenjiaxiang
 * @create: 2023/9/12 10:47
 **/
@Slf4j
public class PoiTlToWordUtils {


    /**
     * 导出文件到本地
     *
     * @param map        数据集合
     * @param nameList   循环增加表格的标识名称集合
     * @param filePath   模版地址
     * @param outPath    输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static void exportWordToLocality(Map<String, Object> map, List<String> nameList, String filePath, String outPath, List<String> removeTags) {
        exportFile(map, nameList, filePath, outPath, null, removeTags);
    }

    /**
     * 导出文件到本地,无循环表格
     *
     * @param map        数据集合
     * @param filePath   模版地址
     * @param outPath    输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static void exportWordToLocality(Map<String, Object> map, String filePath, String outPath, List<String> removeTags) {
        exportFile(map, null, filePath, outPath, null, removeTags);
    }

    /**
     * 导出文件到本地
     *
     * @param map        数据集合
     * @param nameList   循环增加表格的标识名称集合
     * @param filePath   模版地址
     * @param outPath    输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
     * @param configure  自定义配置文件
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static void exportWordToLocality(Map<String, Object> map, List<String> nameList, String filePath, String outPath, Configure configure, List<String> removeTags) {
        exportFile(map, nameList, filePath, outPath, configure, removeTags);
    }

    /**
     * 导出文件到本地,无循环表格
     *
     * @param map        数据集合
     * @param filePath   模版地址
     * @param outPath    输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
     * @param configure  自定义配置文件
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static void exportWordToLocality(Map<String, Object> map, String filePath, String outPath, Configure configure, List<String> removeTags) {
        exportFile(map, null, filePath, outPath, configure, removeTags);
    }

    /**
     * 浏览器下载
     *
     * @param response   response
     * @param map        数据信息
     * @param nameList   循环增加表格的标识名称集合
     * @param filePath   模版文件地址
     * @param fileName   文件名称
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static void exportBrowser(HttpServletResponse response, Map<String, Object> map, List<String> nameList, String filePath, String fileName, List<String> removeTags) {
        exportFile(response, map, nameList, filePath, fileName, null, removeTags);
    }

    /**
     * 浏览器下载,无循环表格
     *
     * @param response   response
     * @param map        数据信息
     * @param filePath   模版文件地址
     * @param fileName   文件名称
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static void exportBrowser(HttpServletResponse response, Map<String, Object> map, String filePath, String fileName, List<String> removeTags) {
        exportFile(response, map, null, filePath, fileName, null, removeTags);
    }

    /**
     * 浏览器下载 支持自定义配置文件
     *
     * @param response   response
     * @param map        数据信息
     * @param nameList   循环增加表格的标识名称集合
     * @param filePath   模版文件地址
     * @param fileName   文件名称
     * @param configure  自定义配置文件
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static void exportBrowser(HttpServletResponse response, Map<String, Object> map, List<String> nameList, String filePath, String fileName, Configure configure, List<String> removeTags) {
        exportFile(response, map, nameList, filePath, fileName, configure, removeTags);
    }

    /**
     * 浏览器下载,无循环表格,支持自定义配置文件
     *
     * @param response   response
     * @param map        数据信息
     * @param filePath   模版文件地址
     * @param fileName   文件名称
     * @param configure  自定义配置文件
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static void exportBrowser(HttpServletResponse response, Map<String, Object> map, String filePath, String fileName, Configure configure, List<String> removeTags) {
        exportFile(response, map, null, filePath, fileName, configure, removeTags);
    }

    /**
     * 文件生成返回 InputStream
     *
     * @param map        数据集合
     * @param nameList   循环增加表格的标识名称集合
     * @param filePath   模版地址
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static InputStream exportFileInputStream(Map<String, Object> map, List<String> nameList, String filePath, List<String> removeTags) {
        return exportFile(map, nameList, filePath, null, removeTags);
    }

    /**
     * 文件生成返回 InputStream ,无循环表格
     *
     * @param map        数据集合
     * @param filePath   模版地址
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static InputStream exportFileInputStream(Map<String, Object> map, String filePath, List<String> removeTags) {
        return exportFile(map, null, filePath, null, removeTags);
    }


    /**
     * 文件生成返回 InputStream
     *
     * @param map        数据集合
     * @param nameList   循环增加表格的标识名称集合
     * @param filePath   模版地址
     * @param configure  自定义配置文件
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static InputStream exportFileInputStream(Map<String, Object> map, List<String> nameList, String filePath, Configure configure, List<String> removeTags) {
        return exportFile(map, nameList, filePath, configure, removeTags);
    }

    /**
     * 文件生成返回 InputStream ,无循环表格
     *
     * @param map        数据集合
     * @param filePath   模版地址
     * @param configure  自定义配置文件
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    public static InputStream exportFileInputStream(Map<String, Object> map, String filePath, Configure configure, List<String> removeTags) {
        return exportFile(map, null, filePath, configure, removeTags);
    }

    /**
     * 导出文件到本地
     *
     * @param map        数据集合
     * @param nameList   循环增加表格的标识名称集合
     * @param filePath   模版地址
     * @param outPath    输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    private static void exportFile(Map<String, Object> map, List<String> nameList, String filePath, String outPath, Configure configure, List<String> removeTags) {
        XWPFTemplate template = null;
        BufferedOutputStream bos = null;
        OutputStream out = null;
        try {
            // 获取Word模板,模板存放路径在项目的resources目录下
            template = common(filePath, map, nameList, configure);
            out = new ByteArrayOutputStream();
            bos = new BufferedOutputStream(out);
            template.write(bos);
            bos.flush();
            out.flush();
            removeTags(template, removeTags);
            template.writeAndClose(Files.newOutputStream(Paths.get(outPath)));
        } catch (Exception e) {
            log.info("[生成word文件]-失败,{}", e.getMessage());
            throw new BusinessRuntimeException(e.getMessage());
        } finally {
            closeStream(template);
            closeStream(bos);
            closeStream(out);
        }
    }


    /**
     * 浏览器下载
     *
     * @param response   response
     * @param map        数据信息
     * @param nameList   循环增加表格的标识名称集合
     * @param filePath   模版文件地址
     * @param fileName   文件名称
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    private static void exportFile(HttpServletResponse response, Map<String, Object> map, List<String> nameList, String filePath, String fileName, Configure configure, List<String> removeTags) {
        XWPFTemplate template = null;
        BufferedOutputStream bos = null;
        OutputStream out = null;
        try {
            // 获取Word模板,模板存放路径在项目的resources目录下
            template = common(filePath, map, nameList, configure);
            // 浏览器端下载 设置响应
            setResponse(response, fileName);
            out = response.getOutputStream();
            bos = new BufferedOutputStream(out);
            removeTags(template, removeTags);
            template.write(bos);
            bos.flush();
            out.flush();
            PoitlIOUtils.closeQuietlyMulti(template, bos, out);
        } catch (Exception e) {
            log.info("[生成word文件]-失败,{}", e.getMessage());
            throw new BusinessRuntimeException(e.getMessage());
        } finally {
            closeStream(template);
            closeStream(bos);
            closeStream(out);
        }
    }

    /**
     * 文件生成返回 InputStream
     *
     * @param map        数据集合
     * @param nameList   循环增加表格的标识名称集合
     * @param filePath   模版地址
     * @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
     */
    private static InputStream exportFile(Map<String, Object> map, List<String> nameList, String filePath, Configure configure, List<String> removeTags) {
        XWPFTemplate template = null;
        ByteArrayOutputStream bos = null;
        try {
            // 获取Word模板,模板存放路径在项目的resources目录下
            template = common(filePath, map, nameList, configure);
            bos = new ByteArrayOutputStream();
            removeTags(template, removeTags);
            template.write(bos);
            return new ByteArrayInputStream(bos.toByteArray());
        } catch (Exception e) {
            log.info("[生成word文件]-失败,{}", e.getMessage());
            throw new BusinessRuntimeException(e.getMessage());
        } finally {
            closeStream(template);
            closeStream(bos);
        }
    }

    private static XWPFTemplate common(String filePath, Map<String, Object> map, List<String> nameList, Configure configure) {
        //获取模版文件的文件流
        XWPFTemplate template;
        InputStream ins = null;
        try {
            ins = getTemplate(filePath);
            //如果有循环添加的数据表格的话 则绑定
            template = CollectionUtils.isNotEmpty(nameList) ? XWPFTemplate.compile(ins, Objects.nonNull(configure) ? configure : getConfig(nameList)).render(map)
                    : XWPFTemplate.compile(ins).render(map);
        } finally {
            closeStream(ins);
        }
        return template;
    }

    private static <T extends Closeable> void closeStream(T ins) {
        if (ins != null) {
            try {
                ins.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }


    /**
     * 绑定循环表格标识
     *
     * @param nameList 标识集合
     * @return 配置
     */
    private static Configure getConfig(List<String> nameList) {
        //使用行循环插件
        LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
        ConfigureBuilder builder = Configure.builder();
        nameList.forEach(name -> builder.bind(name, policy));
        return builder.build();
    }


    /**
     * 【获取网络文件的输入流】
     *
     * @param filePath: 网络文件路径
     * @return java.io.InputStream
     */
    public static InputStream getInputStream(String filePath) {
        InputStream inputStream = null;
        //创建URL
        try {
            URL url = new URL(filePath);
            //试图连接并取得返回状态码
            URLConnection urlconn = url.openConnection();
            urlconn.connect();
            HttpURLConnection httpconn = (HttpURLConnection) urlconn;
            int httpResult = httpconn.getResponseCode();
            if (httpResult == HttpURLConnection.HTTP_OK) {
                inputStream = urlconn.getInputStream();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return inputStream;
    }

    /**
     * 获取模版文件流信息
     *
     * @param filePath 模版路径
     * @return 流
     */
    public static InputStream getTemplate(String filePath) {
        // 获取Word模板,模板存放路径在项目的resources目录下
        ClassPathResource classPathResource = new ClassPathResource(filePath);
        try {
            return classPathResource.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 放入 response
     *
     * @param response 响应
     * @param fileName 文件名称
     */
    private static void setResponse(HttpServletResponse response, String fileName) {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String filePoiName;
        try {
            filePoiName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + filePoiName + ".docx");
    }

    /**
     * 去处表格标签
     *
     * @param template   word
     * @param removeTags 标签集合
     */
    private static void removeTags(XWPFTemplate template, List<String> removeTags) {
        if (CollectionUtils.isNotEmpty(removeTags)) {
            traverseTables(template.getXWPFDocument(), removeTags);
        }
    }

    /**
     * 根据标签去除该表格的 第一行
     *
     * @param document   文件
     * @param removeTags 标签集合
     */
    private static void traverseTables(XWPFDocument document, List<String> removeTags) {
        List<XWPFTable> tables = document.getTables();
        // 递归处理嵌套表格
        tables.forEach(table -> {
            containsMarkerInFirstRow(table, removeTags);
            traverseNestedTables(table, removeTags);
        });
    }

    /**
     * 递归处理嵌套表格
     *
     * @param table      表格
     * @param removeTags 需要去处的表格标签
     */
    private static void traverseNestedTables(XWPFTable table, List<String> removeTags) {
        table.getRows().forEach(row -> row.getTableCells().forEach(cell -> traverseTables(cell, removeTags)));
    }

    /**
     * 传入cell 进行判断当前行是否包含
     */
    private static void traverseTables(XWPFTableCell cell, List<String> removeTags) {
        cell.getTables().forEach(table -> {
            // 判断是否包含标签 是的话则去除表格表头
            containsMarkerInFirstRow(table, removeTags);
            traverseNestedTables(table, removeTags);
        });
    }

    /**
     * 判断是否包含标签 是的话则去除表格表头
     *
     * @param table      表格
     * @param removeTags 需要去处的表格信息
     */
    private static void containsMarkerInFirstRow(XWPFTable table, List<String> removeTags) {
        List<XWPFTableRow> rows = table.getRows();
        int a = 0;
        if (!rows.isEmpty()) {
            for (XWPFTableRow row : rows) {
                if (removeTags.stream().anyMatch(removeTag -> row.getTable().getText().contains(String.format("{{%s}}", removeTag)) && rows.indexOf(row) == 0)) {
                    table.removeRow(0);
                    a = 1;
                }
                if (a > 0) {
                    break;
                }
            }
        }
    }


    private static final String OUT_PATH = "/Users/chenjx/Downloads/zipceshi/0911/";

    private static final String FOP = "/file/elTest123.docx";


    public static void main(String[] args) {
        Map<String, Object> m = new HashMap<>();
        m.put("baseName", "爱就是打卡上");
        m.put("imgUrl", Pictures.ofStream(getInputStream("http://img.crcz.com/allimg/202003/27/1585280268112506.jpg"))
                .size(50, 50).create());
        //try {
        String avatar = "http://img.crcz.com/allimg/202003/27/1585280268105939.jpg," +
                "http://img.crcz.com/allimg/202003/27/1585280268458675.jpg," +
                "http://img.crcz.com/allimg/202003/26/1585192258743890-lp.jpg";
        List<PictureRenderData> imgList = Arrays.stream(avatar.split(",")).map(s -> Pictures.ofStream(getInputStream(s))
                .size(50, 50).create()).collect(Collectors.toList());
        List<Map<String, Object>> par = new ArrayList<>();
        for (int i = 0; i <= 1; i++) {
            Map<String, Object> map = new HashMap<>(16);
            map.put("user", "测试一下吧");
            map.put("pag", "123");
            map.put("img1", imgList);
            par.add(map);
        }

        m.put("imgs", imgList);
        m.put("img", imgList);
        m.put("list", par);
        m.put("list1", par);
        m.put("list3", par);
        m.put("imga", imgList);
        m.put("mo", new HyperlinkTextRenderData("测试超链接", "http://www.baidu.com"));

        List<Map<String, Object>> par1 = new ArrayList<>();
        for (int i = 0; i <= 2; i++) {
            Map<String, Object> map = new LinkedHashMap<>(16);
            map.put("test1", "测试一下吧");
            map.put("test2", "123");
            map.put("test3", imgList);
            par1.add(map);
        }
        exportWordToLocality(m, Arrays.asList("list", "list1", "list3"), FOP, OUT_PATH + UUID.randomUUID() + ".docx", Arrays.asList("remove_tit", "remove_tit1"));

    }


}

生成效果:

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C__jx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值