word poi-tl 表格功能增强,实现表格功能垂直合并

目录

问题

由于在开发功能需求中,word文档需要垂直合并表格,而word模版引擎原有的插件功能只能做行循环,不满足需求;

解决问题

  • 目前选择的poi-tl的模版引擎,在原有的基础上新增自定义插件来实现功能

poi-tl介绍

poi-tl 是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库,你可以非常方便的加入到你的项目中;

Word模板引擎功能 描述
文本 将标签渲染为文本
图片 将标签渲染为图片
表格 将标签渲染为表格
图表 条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)、散点图等图表渲染
If Condition判断 根据条件隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Foreach Loop循环 根据集合循环某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Loop表格行 循环复制渲染表格的某一行
Loop表格列 循环复制渲染表格的某一列
Loop有序列表 支持有序列表的循环,同时支持多级列表
Highlight代码高亮 word中代码块高亮展示,支持26种语言和上百种着色样式
Markdown 将Markdown渲染为word文档
Word批注 完整的批注功能,创建批注、修改批注等
Word附件 Word中插入附件
SDT内容控件 内容控件内标签支持
Textbox文本框 文本框内标签支持
图片替换 将原有图片替换成另一张图片
书签、锚点、超链接 支持设置书签,文档内锚点和超链接功能
Expression Language 完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL
样式 支持有序列表的循环,同时支持多级列表
模板嵌套 模板包含子模板,子模板再包含子模板
模板嵌套 模板包含子模板,子模板再包含子模板
合并 Word合并Merge,也可以在指定位置进行合并
用户自定义函数(插件) 插件化设计,在文档任何位置执行函数

功能实现

引入依赖

xml 复制代码
		<dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.12.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-full</artifactId>
            <version>5.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.5</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.25</version>
        </dependency>
        <!-- spring el表达式 -->
        <dependency>
	  		<groupId>org.springframework</groupId>
		  	<artifactId>spring-expression</artifactId>
		 	 <version>5.3.18</version>
		</dependency>

模版

代码

java 复制代码
@Test
    public void test() throws Exception {
        Configure config = Configure.builder()
                .bind("precipitationInfoList", new ServerMergeTablePolicy())
                .useSpringEL(false).build();
        Map<String, Object> dataMap = new HashMap<String, Object>();
        dataMap.put("precipitationInfoList", getServerMergeTableData());
        ClassPathResource classPathResource = new ClassPathResource("static/word/template.docx");
        try (InputStream resourceInputStream = classPathResource.getInputStream();
             XWPFTemplate template = XWPFTemplate.compile(resourceInputStream, config);) {

            template.render(dataMap);
            template.writeAndClose(new FileOutputStream("output.docx"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private ServerMergeTableData getServerMergeTableData() {

        List<Map<String,Object>> serverDataList = new ArrayList<>();

        Map<String,Object> serverData1 = new HashMap<>();
        serverData1.put("province","广东省");
        serverData1.put("city","深圳市");
        serverData1.put("precipitation","0.3");
        serverDataList.add(serverData1);


        Map<String,Object> serverData2 = new HashMap<>();
        serverData2.put("province","广东省");
        serverData2.put("city","广州市");
        serverData2.put("precipitation","5.1");
        serverDataList.add(serverData2);

        Map<String,Object> serverData3 = new HashMap<>();
        serverData3.put("province","广东省");
        serverData3.put("city","东莞市");
        serverData3.put("precipitation","10");
        serverDataList.add(serverData3);


        Map<String,Object> serverData4 = new HashMap<>();
        serverData4.put("province","湖南");
        serverData4.put("city","长沙");
        serverData4.put("precipitation","10");
        serverDataList.add(serverData4);

        Map<String,Object> serverData6 = new HashMap<>();
        serverData6.put("province","湖南");
        serverData6.put("city","湘潭");
        serverData6.put("precipitation","4.5");
        serverDataList.add(serverData6);

        List<MergeGroupData> groupDataList = new ArrayList<>();
        groupDataList.add(MergeGroupData.builder().indexList(Arrays.asList("0","1","2")).build());
        groupDataList.add(MergeGroupData.builder().indexList(Arrays.asList("3","4")).build());


        List<MergeColumnData> mergeColumns = new ArrayList<>();
        mergeColumns.add(MergeColumnData.builder().groupDataList(groupDataList).mergeColumn(0).build());
        ServerMergeTableData serverMergeTableData = new ServerMergeTableData();
        serverMergeTableData.setMergeColumns(mergeColumns);
        serverMergeTableData.setServerDataList(serverDataList);
        return serverMergeTableData;
    }

效果图

附加(插件实现)

MergeColumnData 对象

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MergeColumnData {

    /**
     * 携带要分组的信息
     */
    private List<MergeGroupData> groupDataList;

    /**
     * 需要合并的列,从0开始
     */
    private Integer mergeColumn;
}

MergeGroupData 类

java 复制代码
/**
 * 合并对象信息
 */
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MergeGroupData {
    /**
     * 名称
     */
    private List<String> indexList;

    public Integer getListSize() {
        if (Objects.isNull(indexList)) {
            return 0;
        }
        return indexList.size();
    }
}

ServerMergeTableData 数据信息

java 复制代码
import lombok.Data;

import java.util.List;

@Data
public class ServerMergeTableData {

    private List<?> serverDataList;

    /**
     * 列合并信息
     */
    private List<MergeColumnData> mergeColumns;
}

ServerMergeTablePolicy 合并插件

java 复制代码
import cn.hutool.core.collection.CollUtil;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.policy.AbstractRenderPolicy;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.render.compute.EnvModel;
import com.deepoove.poi.render.compute.RenderDataCompute;
import com.deepoove.poi.render.processor.DocumentProcessor;
import com.deepoove.poi.render.processor.EnvIterator;
import com.deepoove.poi.resolver.TemplateResolver;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.MetaTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.util.ReflectionUtils;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;

import java.util.Iterator;
import java.util.List;

public class ServerMergeTablePolicy extends AbstractRenderPolicy<ServerMergeTableData> {

    private String prefix;
    private String suffix;
    private boolean onSameLine;

    public ServerMergeTablePolicy() {
        this(false);
    }

    public ServerMergeTablePolicy(boolean onSameLine) {
        this("[", "]", onSameLine);
    }

    public ServerMergeTablePolicy(String prefix, String suffix) {
        this(prefix, suffix, false);
    }

    public ServerMergeTablePolicy(String prefix, String suffix, boolean onSameLine) {
        this.prefix = prefix;
        this.suffix = suffix;
        this.onSameLine = onSameLine;
    }

    @Override
    public void doRender(RenderContext<ServerMergeTableData> context) throws Exception {
        XWPFTemplate template = context.getTemplate();
        ServerMergeTableData mergeTableData = context.getData();
        ElementTemplate eleTemplate = context.getEleTemplate();
        this.renderTo(eleTemplate, mergeTableData, template);
    }

    public void renderTo(ElementTemplate eleTemplate, Object tableData, XWPFTemplate template) {
        RunTemplate runTemplate = (RunTemplate) eleTemplate;
        XWPFRun run = runTemplate.getRun();

        try {

            XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
            XWPFTable table = tagCell.getTableRow().getTable();
            run.setText("", 0);

            if (null == tableData) {
                return;
            }
            ServerMergeTableData serverTableData = (ServerMergeTableData) tableData;
            List data = serverTableData.getServerDataList();
            if (!TableTools.isInsideTable(run)) {
                throw new IllegalStateException(
                        "The template tag " + runTemplate.getSource() + " must be inside a table");
            }

            int templateRowIndex = getTemplateRowIndex(tagCell);
            int tempStartRow = templateRowIndex;
            if (null != data && data instanceof Iterable) {
                Iterator<?> iterator = ((Iterable<?>) data).iterator();
                XWPFTableRow templateRow = table.getRow(templateRowIndex);
                int insertPosition = templateRowIndex;

                TemplateResolver resolver = new TemplateResolver(template.getConfig().copy(prefix, suffix));
                boolean firstFlag = true;
                int index = 0;
                boolean hasNext = iterator.hasNext();
                while (hasNext) {
                    Object root = iterator.next();
                    hasNext = iterator.hasNext();

                    insertPosition = templateRowIndex++;
                    XWPFTableRow nextRow = table.insertNewTableRow(insertPosition);
                    setTableRow(table, templateRow, insertPosition);

                    // double set row
                    XmlCursor newCursor = templateRow.getCtRow().newCursor();
                    newCursor.toPrevSibling();
                    XmlObject object = newCursor.getObject();
                    nextRow = new XWPFTableRow((CTRow) object, table);
                    if (!firstFlag) {
                        // update VMerge cells for non-first row
                        List<XWPFTableCell> tableCells = nextRow.getTableCells();
                        for (XWPFTableCell cell : tableCells) {
                            CTTcPr tcPr = TableTools.getTcPr(cell);
                            CTVMerge vMerge = tcPr.getVMerge();
                            if (null == vMerge) continue;
                            if (STMerge.RESTART == vMerge.getVal()) {
                                vMerge.setVal(STMerge.CONTINUE);
                            }
                        }
                    } else {
                        firstFlag = false;
                    }
                    setTableRow(table, nextRow, insertPosition);

                    RenderDataCompute dataCompute = template.getConfig()
                            .getRenderDataComputeFactory()
                            .newCompute(EnvModel.of(root, EnvIterator.makeEnv(index++, hasNext)));
                    List<XWPFTableCell> cells = nextRow.getTableCells();
                    cells.forEach(cell -> {
                        List<MetaTemplate> templates = resolver.resolveBodyElements(cell.getBodyElements());
                        new DocumentProcessor(template, resolver, dataCompute).process(templates);
                    });
                }
            }

            table.removeRow(templateRowIndex);
            afterloop(table, data);

            //合并表格信息
            mergeTable(serverTableData, tempStartRow, table);
        } catch (Exception e) {
            throw new RenderException("HackLoopTable for " + eleTemplate + " error: " + e.getMessage(), e);
        }
    }

    private void mergeTable(ServerMergeTableData serverMergeTableData, int startRow, XWPFTable xwpfTable) {
        List serverDataList = serverMergeTableData.getServerDataList();
        List<MergeColumnData> mergeColumns = serverMergeTableData.getMergeColumns();
        if (CollUtil.isNotEmpty(mergeColumns)) {
            for (int i = 0; i < serverDataList.size(); i++) {
                for (MergeColumnData mergeColumnData : mergeColumns) {
                    Integer mergeColumn = mergeColumnData.getMergeColumn();
                    List<MergeGroupData> groupDataList = mergeColumnData.getGroupDataList();
                    for (int j = 0; j < groupDataList.size(); j++) {
                        MergeGroupData mergeGroupData = groupDataList.get(j);
                        List<String> indexList = mergeGroupData.getIndexList();
                        int listSize = mergeGroupData.getListSize();
                        if (listSize == 1) {
                            continue;
                        }
                        // 若匹配上 就直接合并
                        if (indexList.contains(i + "")) {
                            int col = i + startRow;
                            int fromRow = i + (startRow - 1) + listSize;
                            TableTools.mergeCellsVertically(xwpfTable, mergeColumn, col, fromRow);
                            groupDataList.remove(j);
                            break;
                        }
                    }
                }
            }
        }
    }

    private int getTemplateRowIndex(XWPFTableCell tagCell) {
        XWPFTableRow tagRow = tagCell.getTableRow();
        return onSameLine ? getRowIndex(tagRow) : (getRowIndex(tagRow) + 1);
    }

    protected void afterloop(XWPFTable table, Object data) {
    }

    @SuppressWarnings("unchecked")
    private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {
        List<XWPFTableRow> rows = (List<XWPFTableRow>) ReflectionUtils.getValue("tableRows", table);
        rows.set(pos, templateRow);
        table.getCTTbl().setTrArray(pos, templateRow.getCtRow());
    }

    private int getRowIndex(XWPFTableRow row) {
        List<XWPFTableRow> rows = row.getTable().getRows();
        return rows.indexOf(row);
    }
}
相关推荐
泉城老铁16 分钟前
springboot实现对接poi 导出excel折线图
java·spring boot·后端
金銀銅鐵34 分钟前
[Java] 如何自动生成简单的 Mermaid 类图
java·后端
纵横八荒1 小时前
Java基础加强13-集合框架、Stream流
java·开发语言
稚辉君.MCA_P8_Java1 小时前
kafka解决了什么问题?mmap 和sendfile
java·spring boot·分布式·kafka·kubernetes
乄bluefox1 小时前
保姆级docker部署nacos集群
java·docker·容器
欣然~1 小时前
百度地图收藏地址提取与格式转换工具 说明文档
java·开发语言·dubbo
玩毛线的包子2 小时前
Android Gradle学习(十三)- 配置读取和文件写入
java
青岛少儿编程-王老师2 小时前
CCF编程能力等级认证GESP—C++6级—20250927
java·c++·算法
一條狗2 小时前
学习日报 20251007|深度解析:基于 Guava LoadingCache 的优惠券模板缓存设计与实现
java·oracle·loadingcache
Miraitowa_cheems3 小时前
LeetCode算法日记 - Day 64: 岛屿的最大面积、被围绕的区域
java·算法·leetcode·决策树·职场和发展·深度优先·推荐算法