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);
    }
}
相关推荐
ylfhpy1 小时前
Java面试黄金宝典30
java·数据库·算法·面试·职场和发展
喻米粒06222 小时前
RabbitMQ消息相关
java·jvm·spring boot·spring·spring cloud·sentinel·java-rabbitmq
Aphelios3804 小时前
Java全栈面试宝典:线程机制与Spring IOC容器深度解析
java·开发语言·jvm·学习·rbac
qq_529835354 小时前
装饰器模式:如何用Java打扮一个对象?
java·开发语言·装饰器模式
日暮南城故里4 小时前
Java学习------源码解析之StringBuilder
java·开发语言·学习·源码
一个public的class7 小时前
什么是 Java 泛型
java·开发语言·后端
士别三日&&当刮目相看7 小时前
JAVA学习*Object类
java·开发语言·学习
快来卷java7 小时前
MySQL篇(一):慢查询定位及索引、B树相关知识详解
java·数据结构·b树·mysql·adb
凸头8 小时前
I/O多路复用 + Reactor和Proactor + 一致性哈希
java·哈希算法
慵懒学者8 小时前
15 网络编程:三要素(IP地址、端口、协议)、UDP通信实现和TCP通信实现 (黑马Java视频笔记)
java·网络·笔记·tcp/ip·udp