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);
    }
}
相关推荐
天若有情6731 分钟前
打破思维定式!C++参数设计新范式:让结构体替代传统参数列表
java·开发语言·c++
亲爱的非洲野猪7 分钟前
从ReentrantLock到AQS:深入解析Java并发锁的实现哲学
java·开发语言
wheelmouse77889 分钟前
如何设置VSCode打开文件Tab页签换行
java·python
yangminlei11 分钟前
Spring Boot——日志介绍和配置
java·spring boot
廋到被风吹走18 分钟前
【Spring】Spring Boot Starter设计:公司级监控SDK实战指南
java·spring boot·spring
码头整点薯条22 分钟前
启动报错:Invalid value type for attribute ‘factoryBeanObjectType‘ 解决方案
java
沛沛老爹23 分钟前
Web开发者进阶AI:Agent Skills-深度迭代处理架构——从递归函数到智能决策引擎
java·开发语言·人工智能·科技·架构·企业开发·发展趋势
工具罗某人25 分钟前
docker快速部署kafka
java·nginx·docker
秋饼27 分钟前
【手撕 @EnableAsync:揭秘 SpringBoot @Enable 注解的魔法开关】
java·spring boot·后端
Good_Starry31 分钟前
Java——正则表达式
java·开发语言·正则表达式