使用 Poi-tl 处理 Word 文档表格数据


使用 Poi-tl 处理 Word 文档表格数据

根据模板文件,替换模板文件里对应的数据,这里使用 poi-tl 进行处理。

poi-tl 是在 poi 的基础上进行封装的,因此使用 poi-tl 时,注意彼此版本对应关系。

https://deepoove.com/poi-tl/

引入架包

<!--poi框架-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.2</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.2</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>5.2.2</version>
</dependency>

<!--注意对应poi的版本依赖:https://deepoove.com/poi-tl/ -->
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.12.2</version>
</dependency>

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version> 
</dependency>

核心代码

//核心代码
ConfigureBuilder builder = Configure.builder();
try (XWPFTemplate xwpfTemplate = XWPFTemplate.compile(file.getInputStream(), builder.build()).render(
    new HashMap<String, Object>() {{
        mapCode.forEach(this::put);
    }})) {

    // 将完成数据渲染的文档写入到输入流
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    xwpfTemplate.write(byteArrayOutputStream);

    // 将输入流转成 multipartFile
    file = fileService.createByteMultipartFile(byteArrayOutputStream, name, name);
} catch (Exception e) {
    setRecord(dto, template, null, 1, "生成文件时发生异常: " + e.getMessage());
    throw new Exception("生成文件时发生异常: " + e.getMessage());
}

前端数据

{
    "mapCode": {
        "ID_dizhi": "CNT20150011",
        "ID_hetongbianhao": "14875.25",
        "ID_riqi": "2024-04-01",
        "ID_tianbiaoriqi": "2024-04-01"
    }
}

Word 模板

在 Word 模板中,只需要在对应的位置添加如下变量形式即可: ,那么最后生成的文件里,变量的位置就不被 CNT20150011 替换。

表格数据

表格数据,这里使用动态表格类:DynamicTableRenderPolicy 进行处理。

定义处理表格的类

**
 * 定义处理表格的插件
 */
public class DetailTablePolicy extends DynamicTableRenderPolicy {

    @Override
    public void render(XWPFTable table, Object object) throws Exception {
        if (null == object) {
            return;
        }
        try {
            // 获取数据
            LinkedHashMap<String, Object> mapValue = (LinkedHashMap<String, Object>) object;

            // 添加表头数据,默认表格第一行为表头
            XWPFTableRow headerRow = table.getRow(0);
            int columnIndex = 0;
            for (String columnName : mapValue.keySet()) {
                XWPFTableCell headerCell = headerRow.getCell(columnIndex);
                // 设置垂直居中
                headerCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
                XWPFParagraph paragraph = headerCell.getParagraphArray(0);
                paragraph.setAlignment(ParagraphAlignment.CENTER);
                XWPFRun run = paragraph.createRun();
                // 设置大小,加粗和列名
                run.setFontSize(11);
                run.setBold(true);
                run.setText(columnName);
                columnIndex++;
            }

            // 获取列数
            int size = 0;
            for (Object value : mapValue.values()) {
                if (value instanceof List) {
                    size = ((List) value).size();
                    break;
                }
                break;
            }

            // 先把列的数据转成行数据
            List<List<String>> values = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                List<String> item = new ArrayList<String>();
                for (Object ob : mapValue.values()) {
                    if (ob instanceof List) {
                        item.add((String) ((List) ob).get(i));
                    }
                }
                values.add(item);
            }

            // 再把行数据按照每行添加进去
            int index = 1;
            for (List<String> list : values) {
                XWPFTableRow row = table.insertNewTableRow(index);
                row.setHeight(27*20);
                columnIndex = 0;
                for (String cellValue : list) {
                    XWPFTableCell cell = row.createCell();
                    // 设置整个表格里的数据垂直居中对齐
                    cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
                    CTTc ctTc = cell.getCTTc();
                    CTP ctp = ctTc.sizeOfPArray() == 0 ? ctTc.addNewP() : ctTc.getPArray(0);
                    XWPFParagraph paragraph = cell.getParagraph(ctp);
                    paragraph.setAlignment(ParagraphAlignment.CENTER);
                    cell.setText(cellValue);
                    columnIndex++;
                }
                index++;
            }
        } catch (Exception e) {
            throw new Exception("模板文件中存在多余的编码!");
        }
    }
}

定义处理表格方法

 ConfigureBuilder builder = Configure.builder();
        mapCode.forEach((k, v) -> {
            // 设置勾选框
            if (v instanceof List) {
                StringBuilder str = new StringBuilder();
                ((List) v).forEach(m -> {
                    TextRenderData data = new TextRenderData();
                    data.setText("R" + m.toString());
                    str.append(data.getText());
                });
                // 勾选框通过设置字体样式来达到显示效果
                mapCode.put(k, new TextRenderData(str.toString(), new Style("Wingdings 2",12)));
            }

            // 遍历表格数据,new DetailTablePolicy() 为每个表格都使用的插件类
            if (v instanceof LinkedHashMap) {
                ((LinkedHashMap) v).forEach((s, x) -> {
                    builder.bind(new DetailTablePolicy(), k);
                });
            }
        });

        // 把数据渲染到文件中去
        MultipartFile file = null;
        String name = DateUtil.format(new Date(), "yyyyMMdd") + String.format("%05d", new Random().nextInt(100000)) + ".docx";
        MultipartFile multipartFile = fileService.createMultipartFile(resUrl.getData().toString(), name, name);
        try (XWPFTemplate xwpfTemplate = XWPFTemplate.compile(multipartFile.getInputStream(), builder.build()).render(
                new HashMap<String, Object>() {{
                    mapCode.forEach(this::put);
                }})) {

            // 将完成数据渲染的文档写入到输入流
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            xwpfTemplate.write(byteArrayOutputStream);

            // 将输入流转成 multipartFile
            file = fileService.createByteMultipartFile(byteArrayOutputStream, name, name);
        } catch (Exception e) {
            setRecord(dto, template, null, 1, "生成文件时发生异常: " + e.getMessage());
            throw new Exception("生成文件时发生异常: " + e.getMessage());
        }

前端数据

// 后端实体类
@ApiModelProperty("编码和填写的数值")
private LinkedHashMap<String, Object> mapCode;
{
    "mapCode": {
        "ID_zhucheguishu": [
            "委托方",
            "注册方"
        ],
        "ID_dizhi": "CNT20150011",
        "ID_hetongbianhao": "14875.25",
        "ID_riqi": "2024-04-01",
        "ID_tianbiaoriqi": "2024-04-01",
        "ID_renyuanxinxi": {
            "序号": [],
            "姓名": [],
            "性别": [],
            "年龄": []
        },
        "ID_suntances": {
            "编号": [
                "R1"
            ],
            "物质名称": [
                "神经酰胺NP"
            ],
            "吨位": [
                "无意义"
            ]
        }
    }
}
// ID_zhucheguishu 表示勾选框选择的值
// ID_renyuanxinxi 和 ID_suntances 对应为表格数据

Word 模板

表格的制作,有个前提,那就是列数是确定的。

延伸思考:指定插入的行

上面的例子中,表格的数据都是从第一行插入的,如果模板中,既有从第一行插入的数据,又有不是从第一行插入的数据,那要如何处理。

修改处理表格的类

**
 * 定义处理表格的插件
 * 与原方法的区别:
 *  // 增加表头行数位置
 *   private int rowIndex; 
 *   public DetailTablePolicy(int rowIndex) {this.rowIndex = rowIndex;}
 *  // 通过传过来的数据决定插入的位置
 *   XWPFTableRow headerRow = table.getRow(rowIndex);
 *  // 行数据紧跟表头位置
 *   int index = rowIndex + 1;
 */
public class DetailTablePolicy extends DynamicTableRenderPolicy {

    // 增加表头行数位置
    private int rowIndex; 

    public DetailTablePolicy(int rowIndex) {
        this.rowIndex = rowIndex;
    }

    @Override
    public void render(XWPFTable table, Object object) throws Exception {
        if (null == object) {
            return;
        }
        try {
            // 获取数据
            LinkedHashMap<String, Object> mapValue = (LinkedHashMap<String, Object>) object;

            // 添加表头数据,根据表头行位置来决定位置
            XWPFTableRow headerRow = table.getRow(rowIndex);
            int columnIndex = 0;
            for (String columnName : mapValue.keySet()) {
                XWPFTableCell headerCell = headerRow.getCell(columnIndex);
                // 设置垂直居中
                headerCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
                XWPFParagraph paragraph = headerCell.getParagraphArray(0);
                paragraph.setAlignment(ParagraphAlignment.CENTER);
                XWPFRun run = paragraph.createRun();
                // 设置大小,加粗和列名
                run.setFontSize(11);
                run.setBold(true);
                run.setText(columnName);
                columnIndex++;
            }

            // 获取列数
            int size = 0;
            for (Object value : mapValue.values()) {
                if (value instanceof List) {
                    size = ((List) value).size();
                    break;
                }
                break;
            }

            // 先把列的数据转成行数据
            List<List<String>> values = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                List<String> item = new ArrayList<String>();
                for (Object ob : mapValue.values()) {
                    if (ob instanceof List) {
                        item.add((String) ((List) ob).get(i));
                    }
                }
                values.add(item);
            }

            // 行数据按紧跟表头位置
            int index = rowIndex + 1;
            for (List<String> list : values) {
                XWPFTableRow row = table.insertNewTableRow(index);
                row.setHeight(27*20);
                columnIndex = 0;
                for (String cellValue : list) {
                    XWPFTableCell cell = row.createCell();
                    // 设置整个表格里的数据垂直居中对齐
                    cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
                    CTTc ctTc = cell.getCTTc();
                    CTP ctp = ctTc.sizeOfPArray() == 0 ? ctTc.addNewP() : ctTc.getPArray(0);
                    XWPFParagraph paragraph = cell.getParagraph(ctp);
                    paragraph.setAlignment(ParagraphAlignment.CENTER);
                    cell.setText(cellValue);
                    columnIndex++;
                }
                index++;
            }
        } catch (Exception e) {
            throw new Exception("模板文件中存在多余的编码!");
        }
    }
}

定义处理表格方法

// 只需要把原方法中的如下代码修改即可
// 原方法
// 遍历表格数据,new DetailTablePolicy() 为每个表格都使用的插件类
if (v instanceof LinkedHashMap) {
    ((LinkedHashMap) v).forEach((s, x) -> {
        builder.bind(new DetailTablePolicy(), k);
    });
}

// 修改后的代码
// 遍历表格数据,new DetailTablePolicy() 为每个表格都使用的插件类
if (v instanceof LinkedHashMap) {
    ((LinkedHashMap) v).forEach((s, x) -> {
        // 这一块处理和前端传值有关系
        String[] split = k.split("_");
        int i = Integer.parseInt(split[2]) - 1;
        builder.bind(new DetailTablePolicy(i), k);
    });
}

修改前端数据

// 原前端传值,表格的编码为:ID_renyuanxinxi 和 ID_suntances
{
    "mapCode": {
        "ID_renyuanxinxi": {
            "序号": [],
            "姓名": [],
            "性别": [],
            "年龄": []
        },
        "ID_suntances": {
            "编号": [
                "R1"
            ],
            "物质名称": [
                "神经酰胺NP"
            ],
            "吨位": [
                "无意义"
            ]
        }
    }
}
// 现在只需要在其后加上数字,对应数字为表示这个表格的第一行数据插入表格的位置,如:ID_renyuanxinxi_1 就表示从第一行插入;ID_suntances_5 就表示从第五行插入。
// 修改后的前端传值
{
    "mapCode": {
        "ID_renyuanxinxi_1": {
            "序号": [],
            "姓名": [],
            "性别": [],
            "年龄": []
        },
        "ID_suntances_5": {
            "编号": [
                "R1"
            ],
            "物质名称": [
                "神经酰胺NP"
            ],
            "吨位": [
                "无意义"
            ]
        }
    }
}

延伸思考:合并指定的列

合并指定的列,需要前端指定合并的行,合并的列(这里默认从第一列开始合并),当传过来的数据当前行有 “//” 这个字符时,则表示当前行需要合并,该行最后一个 “//” 字符所在的位置,表示需要合并列的位置。

修改处理表格的类

**
 * 定义处理表格的插件
 * 与原方法的区别:
 *  // 增加表头行数位置
 *   private int rowIndex; 
 *   public DetailTablePolicy(int rowIndex) {this.rowIndex = rowIndex;}
 *  // 通过传过来的数据决定插入的位置
 *   XWPFTableRow headerRow = table.getRow(rowIndex);
 *  // 行数据紧跟表头位置
 *   int index = rowIndex + 1;
 */
public class DetailTablePolicy extends DynamicTableRenderPolicy {

    // 增加表头行数位置
    private int rowIndex; 

    public DetailTablePolicy(int rowIndex) {
        this.rowIndex = rowIndex;
    }

    @Override
    public void render(XWPFTable table, Object object) throws Exception {
        if (null == object) {
            return;
        }
        try {
            // 获取数据
            LinkedHashMap<String, Object> mapValue = (LinkedHashMap<String, Object>) object;

            // 添加表头数据,根据表头行位置来决定位置
            XWPFTableRow headerRow = table.getRow(rowIndex);
            int columnIndex = 0;
            for (String columnName : mapValue.keySet()) {
                XWPFTableCell headerCell = headerRow.getCell(columnIndex);
                // 设置垂直居中
                headerCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
                XWPFParagraph paragraph = headerCell.getParagraphArray(0);
                paragraph.setAlignment(ParagraphAlignment.CENTER);
                XWPFRun run = paragraph.createRun();
                // 设置大小,加粗和列名
                run.setFontSize(11);
                run.setBold(true);
                run.setText(columnName);
                columnIndex++;
            }

            // 获取列数
            int size = 0;
            for (Object value : mapValue.values()) {
                if (value instanceof List) {
                    size = ((List) value).size();
                    break;
                }
                break;
            }

            // 先把列的数据转成行数据
            List<List<String>> values = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                List<String> item = new ArrayList<String>();
                for (Object ob : mapValue.values()) {
                    if (ob instanceof List) {
                        item.add((String) ((List) ob).get(i));
                    }
                }
                values.add(item);
            }

            // 行数据按紧跟表头位置
            int index = rowIndex + 1;
            for (List<String> list : values) {
                XWPFTableRow row = table.insertNewTableRow(index);
                row.setHeight(27*20);
                columnIndex = 0;
                for (String cellValue : list) {
                    XWPFTableCell cell = row.createCell();
                    // 设置整个表格里的数据垂直居中对齐
                    cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
                    CTTc ctTc = cell.getCTTc();
                    CTP ctp = ctTc.sizeOfPArray() == 0 ? ctTc.addNewP() : ctTc.getPArray(0);
                    XWPFParagraph paragraph = cell.getParagraph(ctp);
                    paragraph.setAlignment(ParagraphAlignment.CENTER);
                    cell.setText(cellValue);
                    columnIndex++;
                }
                index++;
            }

            // 存储需要合并的行和列的位置信息的列表
            List<int[]> mergeInfoList = new ArrayList<>();

            // 遍历数据列表并记录需要合并的行和列的位置信息
            for (int i = 0; i < values.size(); i++) {
                List<String> rowValues = values.get(i);
                int endIndex = -1;
                endIndex = rowValues.lastIndexOf("//");
                if (endIndex != -1) {
                    mergeInfoList.add(new int[]{i+1, endIndex});
                }
            }

            // 遍历需要合并的信息,合并表格
            for (int[] mergeInfo : mergeInfoList) {
                TableTools.mergeCellsHorizonal(table, mergeInfo[0], 0, mergeInfo[1]);
            }
        } catch (Exception e) {
            throw new Exception("模板文件中存在多余的编码!");
        }
    }
}

备注:其它不需要修改。

  • 参考

https://deepoove.com/poi-tl/#_why_poi_tl

https://deepoove.com/poi-tl/apache-poi-guide.html


文章作者: L Q
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 L Q !
  目录