许鹏程
2023-08-08 a2cfd417f673603633c6d46713d320bb1501f88c
xn commit
已添加8个文件
已修改3个文件
690 ■■■■ 文件已修改
doc/img_1.png 补丁 | 查看 | 原始文档 | blame | 历史
doc/img_2.png 补丁 | 查看 | 原始文档 | blame | 历史
doc/img_3.png 补丁 | 查看 | 原始文档 | blame | 历史
doc/print.md 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/product/print/service/PrintRealizeService.java 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/product/print/util/DynamicTableRenderPolicy.java 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/product/print/util/FlowOpinionRenderPolicy.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/product/print/util/PrintPoiUtil.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/product/print/util/PrintTest.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/product/print/util/TableEmptyHandler.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/product/print/util/WordReplaceUtil.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/img_1.png
doc/img_2.png
doc/img_3.png
doc/print.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
# æ‰“印模板配置说明
## æ ‡ç­¾
### {{var}}
    ç”¨äºŽè¾“出变量,如:{{name}},输出变量name的值
#### ![img_2.png](img_2.png)
### {{@var}}
    ç”¨äºŽè¾“出指定流程节点意见
    ä¾‹å­ï¼š
        1.{{@审核}},输出流程中节点名称为“审核”的意见
        2.{{@xxxx-xxxx-xxxx-xxxx}},输出流程中节点uuid为“xxxx-xxxx-xxxx-xxxx”的意见
### {{@var|var2}}
    ç”¨äºŽåŒä¸€ä½ç½®è¾“出不同流程节点意见
    ç”¨æ³•与{{@var}}一致,只是支持多个节点,节点之间用“|”分隔,优先级从左到右
#### ![img_1.png](img_1.png)
## <span id="table">动态表格(子表)</a>
    {{表名}},根据”表名“获取FieldSetEntity中的子表数据,独占一行,渲染完成后会自动删除该行
### å­è¡¨æ ‡ç­¾
        {{$~index~}}: å›ºå®šå†™æ³•,输出子表行号
        {{$字段名}}:输出子表中指定字段的值
### ![img.png](img.png)
## æµç¨‹æ„è§
将所有流程意见输出,输出顺序为办理时间倒序
标签配置与[动态表格(子表)](#table)动态表格(子表)一致
    æ ‡ç­¾ä¸­çš„变量为固定值:"lx_flow_opinion",完整标签为:{{lx_flow_opinion}}
### è¡¨æ ¼å†…容标签
    {{$node_title}} èŠ‚ç‚¹åç§°
    {{$opinion}} èŠ‚ç‚¹æ„è§ï¼Œå¦‚æœ‰ç­¾ååˆ™ä¼šå°†ç­¾åå›¾ç‰‡ä¸€å¹¶è¾“å‡º
    æ›´å¤šå˜é‡å‚见系统流程详情表(product_sys_flow_detail)单中的变量
### åŒä¸€ä½ç½®ä¸åŒèŠ‚ç‚¹æ„è§
####![img_3.png](img_3.png)
src/main/java/com/product/print/service/PrintRealizeService.java
@@ -10,6 +10,8 @@
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.TextRenderData;
import com.deepoove.poi.data.style.Style;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.util.RegexUtils;
import com.product.common.lang.StringUtils;
import com.product.core.cache.DataPoolCacheImpl;
import com.product.core.config.Global;
@@ -23,16 +25,21 @@
import com.product.print.config.CmnConst;
import com.product.print.service.ide.IPrintRealizeService;
import com.product.print.util.DynamicTableRenderPolicy;
import com.product.print.util.FlowOpinionRenderPolicy;
import com.product.print.util.TableEmptyHandler;
import com.product.tool.flow.service.FlowDetailService;
import com.product.util.BaseUtil;
import com.product.util.SystemParamReplace;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -57,7 +64,7 @@
    public static void dataConvertCheckedData(FieldSetEntity fse) {
        TextRenderData selSymbol = new TextRenderData(CmnConst.PRINT_CHECKED_CHAR, new Style(CmnConst.PRINT_FONT, 14));
        TextRenderData unselSymbol = new TextRenderData(CmnConst.PRINT_UNCHECKED_CHAR, new Style(CmnConst.PRINT_FONT, 14));
        TextRenderData unselSymbol = new TextRenderData(CmnConst.PRINT_UNCHECKED_CHAR, new Style(CmnConst.PRINT_FONT, 18));
        //获取表单字段
        Object[] fields = fse.getMeta().getFields();
@@ -186,6 +193,9 @@
        for (int i = 0; i < fields.length; i++) {
            String field = fields[i].toString();
            FieldSetEntity metaEntity = fse.getMeta().getFieldMeta(field);
            if (metaEntity == null) {
                continue;
            }
            String fieldType = metaEntity.getString("field_type");
            //判断是否拥有流程标识
            if ("flowsign".equals(fieldType)) {
@@ -217,7 +227,7 @@
        replaceWord(localTempPathWord, file.getPath(), fse);
        file.delete();
        String replaceParams = SystemParamReplace.replaceParams(printConf.getString(CmnConst.PRINT_FILE_NAME), fse);
        String replaceParams = SystemParamReplace.replaceParams(Optional.ofNullable(printConf.getString(CmnConst.PRINT_FILE_NAME)).orElse(printConf.getString(CmnConst.PRINT_NAME)), fse);
        printConf.setValue(CmnConst.PRINT_FILE_NAME, replaceParams);
        if (isConvertPdf) {
            try {
@@ -261,16 +271,36 @@
        //获取子表数据
        Map<String, DataTableEntity> subDataMap = dataFse.getSubData();
        ConfigureBuilder config = Configure.createDefault().builder();
        config.addPlugin('@', new FlowOpinionRenderPolicy(flowOpinion));
        config.addPlugin('$', new com.deepoove.poi.policy.DynamicTableRenderPolicy() {
            @Override
            public void doRender(RenderContext<Object> context) throws Exception {
                return;
            }
            @Override
            public void render(XWPFTable table, Object data) throws Exception {
                return;
            }
        });
        config.buildGrammerRegex("(#)?([\\w\\u4e00-\\u9fa5]+)(\\.?[\\w\\u4e00-\\u9fa5\\|]*)*(#)?");
        TableEmptyHandler tableEmptyHandler = new TableEmptyHandler();
        if (!CollectionUtil.isEmpty(subDataMap)) {
            for (Map.Entry<String, DataTableEntity> entry : subDataMap.entrySet()) {
                cloneValues.put(entry.getKey(), entry.getValue().getData().stream().map(item -> (Map<String, Object>) ((Map) item.getValues())).collect(Collectors.toList()));
                config.bind(entry.getKey(), new DynamicTableRenderPolicy(entry.getKey()));
                tableEmptyHandler.addTag(entry.getKey());
            }
        }
        if(flowOpinion!=null && flowOpinion.size()>0){
            cloneValues.put("lx_flow_opinion", flowOpinion);
            config.bind("lx_flow_opinion", new DynamicTableRenderPolicy("lx_flow_opinion"));
        if (flowOpinion != null) {
            if (!CollectionUtil.isEmpty(flowOpinion)) {
                cloneValues.put("lx_flow_opinion", flowOpinion);
                config.bind("lx_flow_opinion", new DynamicTableRenderPolicy("lx_flow_opinion"));
            }
            tableEmptyHandler.addTag("lx_flow_opinion");
        }
        config.setValidErrorHandler(tableEmptyHandler);
        try {
            //检查输出文件是否存在,不存在则创建
            FileUtil.touch(outPath);
src/main/java/com/product/print/util/DynamicTableRenderPolicy.java
@@ -1,8 +1,10 @@
package com.product.print.util;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.MetaTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.util.TableTools;
import com.product.common.lang.StringUtils;
@@ -11,18 +13,11 @@
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalAlignRun;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -37,12 +32,14 @@
    private final String indexKey = "~index~";
    public DynamicTableRenderPolicy(String replaceKey) {
    public     DynamicTableRenderPolicy(String replaceKey) {
        this.replaceKey = replaceKey;
    }
    private XWPFRun run;
    private List<MetaTemplate> metaTemplateList;
    @Override
    public void doRender(RenderContext<Object> context) throws Exception {
@@ -55,8 +52,13 @@
            }
            XWPFTableCell cell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
            XWPFTable table = cell.getTableRow().getTable();
            this.metaTemplateList = context.getTemplate().getElementTemplates();
            ElementTemplate eleTemplate = context.getEleTemplate();
            int index = metaTemplateList.indexOf(eleTemplate);
            this.metaTemplateList = metaTemplateList.subList(index + 1, metaTemplateList.size());
            render(table, context.getData());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RenderException("Dynamic render table error:" + e.getMessage(), e);
        }
    }
@@ -124,133 +126,79 @@
            }
            fieldNames[i] = replaceKey;
        }
        //获取fieldRow所在的下标
        int fieldRowIndex = xwpfTable.getRows().indexOf(fieldRow);
        for (int i = 0; i < subTableData.size(); i++) {
            Map<String, Object> map = subTableData.get(i);
            //创建一行在fieldRowIndex下面
            XWPFTableRow row = xwpfTable.insertNewTableRow(fieldRowIndex + 1 + i);
            copyTableRow(row, fieldRow);
            //设置row的属性与fieldRow一致
            row.setHeight(fieldRow.getHeight());
            //遍历字段每个字段创建一个单元格
            for (int j = 0; j < fieldNames.length; j++) {
                //当前单元格
                XWPFTableCell cell;
                //判断row中第j个单元格是否存在
                if (row.getTableCells().size() > j) {
                    cell = row.getCell(j);
        if (subTableData != null && subTableData.size() > 0) {
            for (int i = 0; i < subTableData.size(); i++) {
                Map<String, Object> map = subTableData.get(i);
                XWPFTableRow row;
                if (i == 0) {
                    row = fieldRow;
                } else {
                    cell = row.createCell();
                    //创建一行在fieldRowIndex下面
                    row = xwpfTable.insertNewTableRow(xwpfTable.getRows().size());
                    PrintPoiUtil.copyTableRow(row, fieldRow);
                }
                //设置单元格的值从map中取出
                //判断是否是序号列
                if (indexKey.equals(fieldNames[j])) {
                    cell.setText(String.valueOf(i + 1));
                    continue;
                }
                Object value = map.get(fieldNames[j]);
                if (value == null) {
                    value = "";
                }
                String text = cell.getText();
                //删除单元格中的旧内容
                if (!StringUtils.isEmpty(text)) {
                    List<XWPFParagraph> paragraphs = cell.getParagraphs();
                    if (paragraphs.size() > 1) {
                        cell.removeParagraph(1);
                    }
                    List<XWPFRun> runs = paragraphs.get(0).getRuns();
                    //清空文字
                    for (int k = 0; k < runs.size(); k++) {
                        runs.get(k).setText("", 0);
                    }
                }
                cell.setText(value.toString());
                if ("lx_flow_opinion".equals(this.replaceKey) && "opinion".equals(fieldNames[j]) && !StringUtils.isEmpty(map.get("sign_attachment_uuid"))) {
                    //意见框 æ’入签名图片到单元格右下角位置
                //遍历字段每个字段创建一个单元格
                for (int j = 0; j < fieldNames.length; j++) {
                    //当前单元格
                    XWPFTableCell cell = row.getCell(j);
                    //清空单元格内容
                    XWPFParagraph xwpfParagraph = cell.addParagraph();
                    xwpfParagraph.setAlignment(ParagraphAlignment.RIGHT);
                    XWPFRun run = xwpfParagraph.createRun();
                    String signAttachmentBase64 = map.get("sign_attachment_uuid").toString();
                    //将base64转换字节流
                    byte[] bytes = Base64.getDecoder().decode(signAttachmentBase64.split(",")[1]);
                    //将字节流转换为输入流
                    InputStream inputStream = new ByteArrayInputStream(bytes);
                    try {
                        //换行插入图片
                        run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, "sign.png", Units.toEMU(50), Units.toEMU(20));
                        inputStream.close();
                    } catch (InvalidFormatException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    int index = cell.getParagraphs().indexOf(xwpfParagraph);
                    PrintPoiUtil.copyParagraph(xwpfParagraph, cell.getParagraphs().get(0));
                    //删除所有的run
                    for (int k = xwpfParagraph.getRuns().size() - 1; k >= 0; k--) {
                        xwpfParagraph.removeRun(k);
                    }
                }
                    XWPFRun xwpfRun = xwpfParagraph.createRun();
                    PrintPoiUtil.copyRun(xwpfRun, cell.getParagraphs().get(0).getRuns().get(0));
                    //删除cell中的段落但排除新增的段落
                    for (int k = cell.getParagraphs().size() - 1; k >= 0; k--) {
                        if (k < index) {
                            cell.removeParagraph(k);
                        }
                    }
                    //判断是否是序号列
                    if (indexKey.equals(fieldNames[j])) {
                        cell.setText(String.valueOf(i + 1));
                        continue;
                    }
                    Object value = map.get(fieldNames[j]);
                    if (value == null) {
                        value = "";
                    }
                    xwpfRun.setText(value.toString(), 0);
                    if ("lx_flow_opinion".equals(this.replaceKey) && "opinion".equals(fieldNames[j]) && !StringUtils.isEmpty(map.get("sign_attachment_uuid"))) {
                        //意见框 æ’入签名图片到单元格右下角位置
                        xwpfParagraph = cell.addParagraph();
                        xwpfParagraph.setAlignment(ParagraphAlignment.RIGHT);
                        XWPFRun run = xwpfParagraph.createRun();
                        String signAttachmentBase64 = map.get("sign_attachment_uuid").toString();
                        //将base64转换字节流
                        byte[] bytes = Base64.getDecoder().decode(signAttachmentBase64.split(",")[1]);
                        //将字节流转换为输入流
                        InputStream inputStream = new ByteArrayInputStream(bytes);
                        try {
                            //换行插入图片
                            run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, "sign.png", Units.toEMU(50), Units.toEMU(20));
                            inputStream.close();
                        } catch (InvalidFormatException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } else {
//            åˆ é™¤fieldRow
            int index = xwpfTable.getRows().indexOf(fieldRow);
            xwpfTable.removeRow(index);
        }
        //删除起始行
        xwpfTable.removeRow(startRowIndex);
        //删除fieldRow
        xwpfTable.removeRow(fieldRowIndex - 1);
//        //读取完毕后删除最后一行
//        xwpfTable.removeRow(rows.size() - 1);
        //获取所有的行判断单元格是否有值没有就删除该行
//        List<XWPFTableRow> tableRows = xwpfTable.getRows();
//        List<XWPFTableRow> deleteRows = new ArrayList<>();
//        for (int i = 0; i < tableRows.size(); i++) {
//            XWPFTableRow row = tableRows.get(i);
//            boolean flag = false;
//            for (int j = 0; j < row.getTableCells().size(); j++) {
//                XWPFTableCell cell = row.getTableCells().get(j);
//                if (StringUtils.isNotEmpty(cell.getText())) {
//                    flag = true;
//                    break;
//                }
//            }
//            if (!flag) {
//                deleteRows.add(row);
//            }
//        }
//        //遍历要删除的行获取所在的下标进行删除
//        for (int i = 0; i < deleteRows.size(); i++) {
//            XWPFTableRow row = deleteRows.get(i);
//            int index = xwpfTable.getRows().indexOf(row);
//            xwpfTable.removeRow(index);
//        }
//        //遍历数据集合,每个map对应一行数据
//        for (int i = 0; i < subTableData.size(); i++) {
//            Map<String, Object> map = subTableData.get(i);
//            //创建一行
//            XWPFTableRow row = xwpfTable.createRow();
//            //遍历字段每个字段创建一个单元格
//            for (int j = 0; j < fieldNames.length; j++) {
//                //当前单元格
//                XWPFTableCell cell;
//                //判断row中第j个单元格是否存在
//                if (row.getTableCells().size() > j) {
//                    cell = row.getTableCells().get(j);
//                } else {
//                    cell = row.createCell();
//                }
//                //设置单元格的值从map中取出
//                //判断是否是序号列
//                if (indexKey.equals(fieldNames[j])) {
//                    cell.setText(String.valueOf(i + 1));
//                    continue;
//                }
//                Object value = map.get(fieldNames[j]);
//                if (value == null) {
//                    value = "";
//                }
//                //设置单元格的值
//                cell.setText(value.toString());
//            }
//        }
    }
    /**
src/main/java/com/product/print/util/FlowOpinionRenderPolicy.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,112 @@
package com.product.print.util;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.deepoove.poi.data.TextRenderData;
import com.deepoove.poi.policy.AbstractRenderPolicy;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.xwpf.BodyContainer;
import com.deepoove.poi.xwpf.BodyContainerFactory;
import com.product.common.lang.StringUtils;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Base64;
import java.util.Optional;
/**
 * è‡ªå®šä¹‰è§£æž{{@var}}标签
 *
 * @Author cheng
 * @Date 2023/6/27 15:50
 * @Desc
 */
public class FlowOpinionRenderPolicy extends AbstractRenderPolicy<TextRenderData> {
    /**
     * æ„è§åˆ—表
     */
    private JSONArray opinionArray;
    public FlowOpinionRenderPolicy(JSONArray opinionArray) {
        this.opinionArray = opinionArray;
    }
    @Override
    public void doRender(RenderContext<TextRenderData> context) throws Exception {
        XWPFRun run = context.getRun();
        //标签名称
        String tagName = context.getEleTemplate().getTagName();
        JSONObject opinion=null;
        //支持多个标签名称,用|分隔 ä¾‹å¦‚:{{@var1|var2|var3...}} æŒ‰ç…§é¡ºåºæŸ¥æ‰¾æ„è§åˆ—表
        for (String name : tagName.split("\\|")) {
            opinion = getOpinion(name);
            if(opinion!=null){
                break;
            }
        }
        if (opinion == null) {
            //如果意见列表中没有找到,则直接替换为空
            run.setText("", 0);
            return;
        }
        XWPFParagraph parent = (XWPFParagraph) run.getParent();
        XWPFRun newRun = run;
        //判断是否有签名
        if (!StringUtils.isEmpty(opinion.getString("sign_attachment_uuid"))) {
            XWPFParagraph paragraph = createParagraph(run);
            //设置签名的段落居右
            paragraph.setAlignment(ParagraphAlignment.RIGHT);
            //插入一个新的run
            newRun = paragraph.createRun();
            String signAttachmentBase64 = opinion.getString("sign_attachment_uuid");
            String[] split = signAttachmentBase64.split(",");
            //将base64转换字节流
            byte[] bytes = Base64.getDecoder().decode(split[split.length - 1]);
            //将字节流转为输入流
            InputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            //插入图片
            newRun.addPicture(byteArrayInputStream, XWPFDocument.PICTURE_TYPE_PNG, "sign.png", Units.toEMU(50), Units.toEMU(20));
            byteArrayInputStream.close();
            newRun.setText("", 0);
        }
        XWPFParagraph paragraph = createParagraph(newRun);
        //段落样式与原始段落一致
        paragraph.getCTP().setPPr(parent.getCTP().getPPr());
        //设置创建新的run
        XWPFRun textRun = paragraph.createRun();
        //复制原始run
        PrintPoiUtil.copyRun(textRun, run);
        //设置run的内容
        textRun.setText(Optional.ofNullable(opinion.getString("opinion")).orElse("未填写办理意见"), 0);
        //删除段落parent,如果只删除run,则会导致段落中还有一个空的run
        parent.getCTP().newCursor().removeXml();
    }
    public XWPFParagraph createParagraph(XWPFRun run) {
        //获取body容器
        BodyContainer bodyContainer = BodyContainerFactory.getBodyContainer(run);
        //插入一个新的段落
        XWPFParagraph paragraph = bodyContainer.insertNewParagraph(run);
        return paragraph;
    }
    public JSONObject getOpinion(String tagName) {
        //先用tagName去意见列表中查找node_uuid是否存在,不存在则使用node_title去查找
        for (int i = 0; i < opinionArray.size(); i++) {
            JSONObject opinion = opinionArray.getJSONObject(i);
            if (tagName.equals(opinion.getString("node_uuid"))) {
                return opinion;
            }
        }
        for (int i = 0; i < opinionArray.size(); i++) {
            JSONObject opinion = opinionArray.getJSONObject(i);
            if (tagName.equals(opinion.getString("node_title"))) {
                return opinion;
            }
        }
        return null;
    }
}
src/main/java/com/product/print/util/PrintPoiUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,138 @@
package com.product.print.util;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.usermodel.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
 * @Author cheng
 * @Date 2023/8/5 16:40
 * @Desc
 */
public class PrintPoiUtil {
    /**
     * å¤åˆ¶è¡Œï¼Œä»Žsource到target
     *
     * @param target
     * @param source
     */
    public static void copyTableRow(XWPFTableRow target, XWPFTableRow source) {
        // å¤åˆ¶æ ·å¼
        if (source.getCtRow() != null) {
            target.getCtRow().setTrPr(source.getCtRow().getTrPr());
        }
        // å¤åˆ¶å•元格
        for (int i = 0; i < source.getTableCells().size(); i++) {
            XWPFTableCell cell1 = target.getCell(i);
            XWPFTableCell cell2 = source.getCell(i);
            if (cell1 == null) {
                cell1 = target.addNewTableCell();
            }
            copyTableCell(cell1, cell2);
        }
    }
    /**
     * å¤åˆ¶å•元格,从source到target
     *
     * @param target
     * @param source
     */
    public static void copyTableCell(XWPFTableCell target, XWPFTableCell source) {
        // åˆ—属性
        if (source.getCTTc() != null) {
            target.getCTTc().setTcPr(source.getCTTc().getTcPr());
        }
        // åˆ é™¤æ®µè½
        for (int pos = 0; pos < target.getParagraphs().size(); pos++) {
            target.removeParagraph(pos);
        }
        // æ·»åŠ æ®µè½
        for (XWPFParagraph sp : source.getParagraphs()) {
            XWPFParagraph targetP = target.addParagraph();
            copyParagraph(targetP, sp);
        }
    }
    /**
     * å¤åˆ¶å›¾ç‰‡åˆ°target
     *
     * @param target
     * @param picture
     * @throws IOException
     * @throws InvalidFormatException
     */
    public static void copyPicture(XWPFRun target, XWPFPicture picture) throws IOException, InvalidFormatException {
        String filename = picture.getPictureData().getFileName();
        InputStream pictureData = new ByteArrayInputStream(picture
                .getPictureData().getData());
        int pictureType = picture.getPictureData().getPictureType();
        int width = (int) picture.getCTPicture().getSpPr().getXfrm().getExt()
                .getCx();
        int height = (int) picture.getCTPicture().getSpPr().getXfrm().getExt()
                .getCy();
        // target.addBreak();
        target.addPicture(pictureData, pictureType, filename, width, height);
        // target.addBreak(BreakType.PAGE);
    }
    /**
     * å¤åˆ¶æ®µè½ï¼Œä»Žsource到target
     *
     * @param target
     * @param source
     */
    public static void copyParagraph(XWPFParagraph target, XWPFParagraph source) {
        // è®¾ç½®æ®µè½æ ·å¼
        target.getCTP().setPPr(source.getCTP().getPPr());
        // ç§»é™¤æ‰€æœ‰çš„run
        for (int pos = target.getRuns().size() - 1; pos >= 0; pos--) {
            target.removeRun(pos);
        }
        // copy æ–°çš„run
        for (XWPFRun s : source.getRuns()) {
            XWPFRun targetrun = target.createRun();
            copyRun(targetrun, s);
        }
    }
    /**
     * å¤åˆ¶RUN,从source到target
     *
     * @param target
     * @param source
     */
    public static void copyRun(XWPFRun target, XWPFRun source) {
        // è®¾ç½®run属性
        target.getCTR().setRPr(source.getCTR().getRPr());
        // è®¾ç½®æ–‡æœ¬
        target.setText(source.text());
        // å¤„理图片
        List<XWPFPicture> pictures = source.getEmbeddedPictures();
        for (XWPFPicture picture : pictures) {
            try {
                copyPicture(target, picture);
            } catch (InvalidFormatException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println(source.getColor());
        System.out.println(target.getColor());
    }
}
src/main/java/com/product/print/util/PrintTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.product.print.util;
import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
 * @Author cheng
 * @Date 2023/8/5 14:18
 * @Desc
 */
public class PrintTest {
    static String templatePath = "d:\\desktop\\APP_NOTICE_DECIDE.docx";
    static String outPath = "d:\\desktop\\APP_NOTICE_DECIDE_out.docx";
    public static void main(String[] args) throws IOException {
        Map<String, Object> values = new HashMap<>();
        values.put("name", "许鹏程");
        ConfigureBuilder config = Configure.createDefault().builder();
        JSONArray array = new JSONArray();
        JSONObject object=new JSONObject();
        object.put("node_uuid","flow");
        object.put("node_title","node_title");
        object.put("node_content","node_content");
        object.put("opinion","opinion");
        object.put("sign_attachment_uuid","sign_attachment_uuid");
        array.add(object);
        config.addPlugin('@', new FlowOpinionRenderPolicy(array));
        FileUtil.touch(outPath);
        XWPFTemplate.compile(templatePath, config.build()).render(values).writeToFile(outPath);
    }
}
src/main/java/com/product/print/util/TableEmptyHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
package com.product.print.util;
import cn.hutool.core.util.ArrayUtil;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.template.run.RunTemplate;
import com.product.core.exception.BaseException;
import com.product.print.config.CmnCode;
import org.apache.poi.xwpf.usermodel.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * @Author cheng
 * @Date 2023/8/7 10:04
 * @Desc è¡¨æ ¼æ•°æ®ç©ºæ—¶
 */
public class TableEmptyHandler extends Configure.ClearHandler {
    private String[] tagName;
    public TableEmptyHandler() {
    }
    public void addTag(String tagName) {
        if (this.tagName == null) {
            this.tagName = new String[]{};
        }
        this.tagName = ArrayUtil.append(this.tagName, tagName);
    }
    @Override
    public void handler(RenderContext<?> context) {
        String tagName = context.getEleTemplate().getTagName();
        if (this.tagName == null || ArrayUtil.indexOf(this.tagName, tagName) == -1) {
            super.handler(context);
            return;
        }
        RunTemplate runTemplate = (RunTemplate) context.getEleTemplate();
        XWPFRun run = runTemplate.getRun();
        XWPFTableCell cell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
        XWPFTable xwpfTable = cell.getTableRow().getTable();
        doRemove(xwpfTable, tagName);
    }
    private void doRemove(XWPFTable xwpfTable, String tagName) {
        List<XWPFTableRow> rows = xwpfTable.getRows();
        //读取rows中的内容
        String tableExpression = "{{" + tagName + "}}";
        //获取表格起始行和结束行
        int startRowIndex = -1;
        rows:
        for (int i = 0; i < rows.size(); i++) {
            XWPFTableRow row = rows.get(i);
            for (int j = 0; j < row.getTableCells().size(); j++) {
                XWPFTableCell cell = row.getTableCells().get(j);
                String text = cell.getText();
                if (tableExpression.equals(text)) {
                    startRowIndex = i;
                    break rows;
                }
            }
        }
        if (startRowIndex == -1) {
            return;
        }
        //在表格中查找子表字段以{{$开头}}以}}结尾的内容
        String regex = "\\{\\{\\$[a-zA-Z0-9_]+\\}\\}";
        Pattern pattern = Pattern.compile(regex);
        //字段所在行
        XWPFTableRow fieldRow = null;
        for (int i = startRowIndex; i < rows.size(); i++) {
            XWPFTableRow row = rows.get(i);
            for (int j = 0; j < row.getTableCells().size(); j++) {
                XWPFTableCell cell = row.getTableCells().get(j);
                String text = cell.getText();
                Matcher matcher = pattern.matcher(text);
                if (matcher.find()) {
                    fieldRow = row;
                    i = rows.size();
                    break;
                }
            }
        }
        //删除起始行
        xwpfTable.removeRow(startRowIndex);
        if (fieldRow == null) {
            return;
        }
        //获取字段所在行的索引
        int fieldRowIndex = xwpfTable.getRows().indexOf(fieldRow);
        //删除起始行到字段所在行之间的所有行
        xwpfTable.removeRow(fieldRowIndex);
    }
}
src/main/java/com/product/print/util/WordReplaceUtil.java
@@ -22,11 +22,7 @@
        values.put("name", "许鹏程");
        values.put("n1", 101);
        values.put("n2", "20-1");
        XWPFTemplate template = XWPFTemplate.compile("D:\\Desktop\\APP_NOTICE_DECIDE.docx").render(values);
        File file = new File("D:\\Desktop\\test.docx");
        OutputStream out = new ByteArrayOutputStream();
        template.write(out);
        template.close();
    }