java实现word文档动态表格复杂操作-附完整实例

一、先看效果

1.1、实现前(word模板)

在这里插入图片描述

1.2、实现后(最新版-已新增样式)

在这里插入图片描述

额外拓展内容(代码未展示,需要的自行到项目查看)

在这里插入图片描述
在这里插入图片描述

二、测试地址

目的:主要为了实现表格动态横竖合并等操作。
实现过程:定义一个类TemplateTableRenderPolicy继承DynamicTableRenderPolicy类插件重写方法实现动态渲染表格
详细记录了每一步操作,图文结合,可以更好的理解这个渲染插件的原理。
【gitee地址跳转】
【gitub地址跳转】
在这里插入图片描述

三、贴出部分代码

3.1、pom.xml

官方文档: http://deepoove.com/poi-tl/#_%E5%9B%BE%E7%89%87

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

3.2、测试类(PoiDemoApplicationTests)

   @Test
    void contextLoads()  {
        try {
        // 一、初始化数据
        // 1)初始化模板数据-实际是去数据库查询-自行整合
          Templates templates = initTemplates();
        // 2)初始化家庭成员信息-实际是去数据库查询-自行整合
        List<FamilyMember> familyMemberList = initFamilyMember();
        // 3)初始化工作情况-实际是去数据库查询-自行整合
        List<Going> goingList = initGoing();

        // 二、初始化动态数据-并整合一个完整的对象
        // 1)处理动态数据->转为RowRenderData类型即List<Going>->List<RowRenderData>
        TemplateRowRenderData templateRowRenderData = new TemplateRowRenderData(familyMemberList,goingList);
        // 2)完整数据
        TemplateData templateData = new TemplateData(templates,templateRowRenderData);

        // 三、绑定插件
        // 1)插件绑定-【TemplateTableRenderPolicy】插件中data能获取到【TemplateRowRenderData】动态数据的关键
        // 注意:模板中也是根据该字段进行渲染:templateRowRenderData 如图resources/pictures/images1.jpg   特别注意不要用到了中文画框花,鼠鼠我郁闷了一早上也想不明道插件为什么不生效
        Configure config = Configure.builder().bind("templateRowRenderData", new TemplateTableRenderPolicy()).build();

        //  四、导出
        ClassPathResource classPathResource = new ClassPathResource("templates" + File.separator + "template.docx");
        XWPFTemplate template = XWPFTemplate.compile(classPathResource.getInputStream(),config).render(
                templateData);
        Assertions.assertNotNull(template);
        // 通过浏览器下载自行整合下即可
        // controller获取HttpServerResponse
            template.writeAndClose(Files.newOutputStream(Paths.get("C:\\Users\\WUDI\\Desktop\\export-word.docx")));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

3.3、初始化数据

  private Templates initTemplates() {
        // 填充一些基本信息
        Templates templates = new Templates();
        templates.setName("牧羊人OVO");
        templates.setAliases("牧羊人OVo");
        templates.setDeptName("青龙小组");
        templates.setSexName("未知");
        templates.setPeoples("汉族");
        templates.setBirth("2000");
        templates.setCulture("小学");
        return templates;
    }

    /**
     * 测试数据、实际是需要查询数据库的
     * @return
     */
    private List<FamilyMember> initFamilyMember() {
        List<FamilyMember> familyMemberList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            FamilyMember familyMember = new FamilyMember();
            familyMember.setRelationship("关系" + i);
            familyMember.setName("姓名" + i);
            familyMember.setPosition("职位" + i);
            familyMemberList.add(familyMember);
        }
        return familyMemberList;
    }

    /**
     * 测试数据
     *
     * @return
     */
    private List<Going> initGoing() {
        List<Going> goingList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            Going going = new Going();
            going.setCompany("工作单位" + i);
            going.setAddress("地址" + i);
            going.setPhone("程联系电话" + i);
            goingList.add(going);
        }
        return goingList;
    }

3.4、初始化需要动态渲染表格

import com.deepoove.poi.data.*;
import com.deepoove.poi.data.style.*;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.muyangren.poidemo.entity.FamilyMember;
import com.muyangren.poidemo.entity.Going;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Data
public class TemplateRowRenderData {
    /**
     * 家庭人员
     */
    private List<RowRenderData> familyRowRenderDataList;

    /**
     * 工作情况
     */
    private List<RowRenderData> goingRowRenderDataList;

    private RowStyle rowStyle;

    public TemplateRowRenderData(List<FamilyMember> familyMemberList, List<Going> goingList) {
        // 初始化样式
        initStyle();
        // 初始化动态数据
        initData(familyMemberList, goingList);
    }

    private void initStyle() {
        // 此处定义样式的优先级高:想看样式获取顺序可以看【TemplateTableRenderPolicy】中的【TableRenderPolicy.Helper.renderRow(xwpfTable.getRow(familyMemberRow), familyRowRenderDataList.get(i))】 谢谢你作者 很规范 nmd

        // 字体样式
        Style style = new Style("宋体", 10);

        // 段落样式
        ParagraphStyle paragraphStyle = new ParagraphStyle();
        paragraphStyle.setDefaultTextStyle(style);
        // ps:这里才是字体居中对齐
        paragraphStyle.setAlign(ParagraphAlignment.CENTER);

        // 表格样式
        CellStyle cellStyle = new CellStyle();
        // ps:表格也需要居中,否则字体不在正中间,会偏上
        cellStyle.setVertAlign(XWPFTableCell.XWPFVertAlign.CENTER);
        cellStyle.setDefaultParagraphStyle(paragraphStyle);

        // 行样式
        this.rowStyle = new RowStyle();
        rowStyle.setDefaultCellStyle(cellStyle);
    }

    private void initData(List<FamilyMember> familyMemberList, List<Going> goingList) {
        List<RowRenderData> newFamilyRowRenderDataList = new ArrayList<>();
        List<RowRenderData> newGoingRowRenderDataList = new ArrayList<>();

        // 判空-家庭人员
        if (CollectionUtils.isNotEmpty(familyMemberList)) {
            for (FamilyMember familyMember : familyMemberList) {
                // 创建一行五表格(根据实际情况来哈)
                List<CellRenderData> cellDataList = new ArrayList<>();
                // 留两个空格(为什么要留两个空白格,大家可以试试自己去添加下行就知道了) 如图:resources/pictures/images3.jpg|images2.jpg
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText("")));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText("")));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(familyMember.getRelationship())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(familyMember.getName())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(familyMember.getPosition())));
                RowRenderData rowRenderData = new RowRenderData();
                // 样式
                rowRenderData.setRowStyle(rowStyle);
                rowRenderData.setCells(cellDataList);
                newFamilyRowRenderDataList.add(rowRenderData);
            }
            this.familyRowRenderDataList = newFamilyRowRenderDataList;
        }else {
            // 要是不存在传null值的话,插件里就要多一层判断了
            this.familyRowRenderDataList= Collections.emptyList();
        }

        // 判空-工作情况
        if (CollectionUtils.isNotEmpty(goingList)) {
            for (Going going : goingList) {
                // 创建一行四表格(根据实际情况来哈)
                List<CellRenderData> cellDataList = new ArrayList<>();
                // 保留一个空格(如【家庭成员所示】)
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText("")));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(going.getCompany())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(going.getAddress())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(going.getPhone())));
                RowRenderData rowRenderData = new RowRenderData();
                // 样式
                rowRenderData.setRowStyle(rowStyle);
                rowRenderData.setCells(cellDataList);
                newGoingRowRenderDataList.add(rowRenderData);
            }
            this.goingRowRenderDataList = newGoingRowRenderDataList;
        }else {
            this.goingRowRenderDataList= Collections.emptyList();
        }
    }
}

3.5、定义一个类实现插件接口并重写

import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;

import java.util.List;

@NoArgsConstructor
public class TemplateTableRenderPolicy extends DynamicTableRenderPolicy {

    @Override
    public void render(XWPFTable xwpfTable, Object data) throws Exception {
        if (null == data) {
            return;
        }
     //-----------------------------------先别急,一行一行看下去-------------------------------------------------------
        // 因为我们前面用Config绑定了【List<RowRenderData>】渲染规则
        TemplateRowRenderData templateRowRenderData = (TemplateRowRenderData) data;

        // 一、处理家庭成员数据
        List<RowRenderData> familyRowRenderDataList = templateRowRenderData.getFamilyRowRenderDataList();
        if (CollectionUtils.isNotEmpty(familyRowRenderDataList)) {
            // 1)计算家庭成员表头所在行数 如图:resource/pictures/images4.jpg
             int familyMemberRow = 9;
            // 2)删掉空白内容:xwpfTable.removeRow是从下标0开始计算的,所以这里删除的是空白内容如图:resource/pictures/images5.jpg
            xwpfTable.removeRow(familyMemberRow);
            // 3)获取该表家庭成员表头高度 【familyMemberRow-1】即家庭成员表头的下标  作用:统一高度
            XWPFTableRow xwpfTableRow = xwpfTable.getRow(familyMemberRow-1);
            // 4)循环插入行 (倒序插入)ps:这里是一直在第9行插入表格。
            for (int i = familyRowRenderDataList.size() - 1; i > -1; i--) {
                // 4.1)插入表格
                XWPFTableRow insertNewTableRow = xwpfTable.insertNewTableRow(familyMemberRow);
                // 4.1.1)控制表格高度
                insertNewTableRow.setHeight(xwpfTableRow.getHeight());
                // 4.2)每行添加11个表格,这里需要根据实际情况填充。秉持多则退少补原则
                // 如图:假如我添加12个 resource/pictures/images6.jpg
                // 如图:假如我添加10个 resource/pictures/images7.jpg
                // 注:想看效果时记得注释下面的代码 【TableTools.mergeCellsHorizonal】,【TableTools.mergeCellsVertically】
                for (int j = 0; j < 11; j++) {
                    insertNewTableRow.createCell();
                }

                // 基本信息占1格,家庭成员占1个,关系、姓名、职位各占3 刚好11格
                // 5)合并上面创建的11个单元格  fromCol-toCol 且 fromCol(起始) < toCol(结束)
                // 5.1)【关系】下标为2,所以2(fromCol)合并至4(toCol)的单元格
                TableTools.mergeCellsHorizonal(xwpfTable, familyMemberRow, 2, 4);
                // 5.2)【姓名】下标为3,所以3(fromCol)合并至5(toCol)的单元格:注意:按上一个合并后的结果再数格子并合并
                TableTools.mergeCellsHorizonal(xwpfTable, familyMemberRow, 3, 5);
                // 5.3)【职位】下标为4,所以4(fromCol)合并至6(toCol)的单元格:注意:按上一个合并后的结果再数格子并合并
                TableTools.mergeCellsHorizonal(xwpfTable, familyMemberRow, 4, 6);

                // 6) 渲染数据(表格对齐后在进行渲染数据)
                TableRenderPolicy.Helper.renderRow(xwpfTable.getRow(familyMemberRow), familyRowRenderDataList.get(i));
            }
            // 7)跨行合并行 参数说明: 1-操作表格方法 2-需要合并行 3-开始合并列 4-结束合并列
            // 7.1) 合并前:resource/pictures/images8.jpg
            // 7.2) 合并后:resource/pictures/images9.jpg
            int fromRow = familyMemberRow - 1;
            // 7.3)合并下标为1的行 合并列为fromRow->familyRowRenderDataList.size() + fromRow
            // 7.4)合并下标为0的行 合并列为0->familyRowRenderDataList.size() + fromRow
            TableTools.mergeCellsVertically(xwpfTable, 1, fromRow, familyRowRenderDataList.size() + fromRow);
            TableTools.mergeCellsVertically(xwpfTable, 0, 0, familyRowRenderDataList.size() + fromRow);
        }
//---------------------------------------以上为家庭成员的数据渲染------------------------------------------------------------

        // 二、处理工作情况数据-关键处此篇不给出解析,大家根据【家庭成员】 自行填写
        List<RowRenderData> goingRowRenderDataList = templateRowRenderData.getGoingRowRenderDataList();
        // 1)判空
        if (CollectionUtils.isNotEmpty(goingRowRenderDataList)) {
            // 2)工作情况所在行数 ps:其实我们从0开始数的话、可以省很多事
            int goingMemberRow = 11;
            if (!CollectionUtils.isEmpty(familyRowRenderDataList)) {
                goingMemberRow = 11 + familyRowRenderDataList.size() - 1;
            }
            // 3)
            xwpfTable.removeRow(goingMemberRow);
            // 4)
            XWPFTableRow xwpfTableRow = xwpfTable.getRow(goingMemberRow-1);
            // 5)循环插入行(倒序插入)
            for (int i = goingRowRenderDataList.size() - 1; i > -1; i--) {
                XWPFTableRow insertNewTableRow = xwpfTable.insertNewTableRow(goingMemberRow);
                insertNewTableRow.setHeight(xwpfTableRow.getHeight());
                // 6)
                for (int j = 0; j < 11; j++) {
                    insertNewTableRow.createCell();
                }
                // 7)
                // 7.1)合并表格
                TableTools.mergeCellsHorizonal(xwpfTable, goingMemberRow, 1, 2);
                // 7.2)合并表格
                TableTools.mergeCellsHorizonal(xwpfTable, goingMemberRow, 2, 7);
                // 7.3)合并表格
                TableTools.mergeCellsHorizonal(xwpfTable, goingMemberRow, 3, 4);
                // 8)渲染数据
                TableRenderPolicy.Helper.renderRow(xwpfTable.getRow(goingMemberRow), goingRowRenderDataList.get(i));
            }
            // 9)合并行
            TableTools.mergeCellsVertically(xwpfTable,0,goingMemberRow-1,goingMemberRow +goingRowRenderDataList.size()-1);
        }
    }
}

四、项目整体路径

在这里插入图片描述

  • 如果该篇文章对您有帮助 麻烦点个赞以及star支持下哈
  • 11
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

牧羊人Ovo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值