POI 通过模板生成word文件

1 篇文章 0 订阅

核心思想:设计占位符号,插入到word模板中占位,当生成word文件时,通过正则表达式使用数据替换预定的占位符号。

环境介绍

本文以springboot的demo为例,构建工具采用gradle,需要依赖的jar如下:

dependencies {
    compile group: 'org.apache.poi', name: 'poi', version: '3.17'
    compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.17'
    compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1'
    compile group: 'commons-beanutils', name: 'commons-beanutils', version: '1.9.3'

    implementation 'org.springframework.boot:spring-boot-starter'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

设计占位符

本文以三种占位符为例:

  • 普通变量:  ${变量名称}
  • IF 控制语句:if{变量名称},变量的值为 Boolean 类型,控制 table 的显示与隐藏。
  • For 循环语句:for{变量名称;占位符行的索引值} ,表格的行是动态创建的,值与占位符行对应。

设计模板

自定义一个模板,如下:

替换占位符

核心代码,如下:

package com.example.demo.utils;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.*;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * POI 模板生成word工具类
 * @author 赏花归去
 * @Date 2019/8/26 11:45
 */
public class WordUtil {
    //占位符
    public static final String[] param_placeholder = {"${","}"};
    private static final String[] if_placeholder = {"if{","}"};
    private static final String[] for_placeholder = {"for{","}"};
    //表格控制行的索引下标
    private static final int tableControlIndex = 0;
    //缓存文件的前缀和后缀
    private static final String tempFilePrefix = "word_temp_";
    private static final String tempFileSuffix = ".docx";


    //获取占位符的正则表达式
    public static Pattern getPattern(String[] placeholder){
        String[] characters = {"$","(",")","*","+",".","[","?","^","{","|"};
        String start = placeholder[0];
        String end = placeholder[1];
        for (String character : characters) {
            String content = "\\" + character;
            start = start.replace(character,content);
            end = end.replace(character,content);
        }
        String reg = start + ".+?" + end;
        return Pattern.compile(reg);
    }

    /**
     * 生成word文件
     * @param objParam
     * @param listParam
     * @param template
     * @return
     * @throws Exception
     */
    public static File generateWordFile(Object objParam, Map<String, List> listParam, String template) {
        try {
            //获取模板文档对象
            XWPFDocument document = new XWPFDocument(new FileInputStream(new ClassPathResource(template).getFile()));
            //对象转Map<String,String>
            Map<String, String> paramMap = getMap(objParam);
            //替换段落中的变量
            replaceParagraphs(document, paramMap);
            // 替换表格中的变量
            Iterator<XWPFTable> tableIte = document.getTablesIterator();
            while (tableIte.hasNext()) {
                XWPFTable table = tableIte.next();
                //获取第一个单元格内容
                XWPFTableRow firstRow = table.getRow(tableControlIndex);
                XWPFTableCell firstCell = firstRow.getCell(0);
                String firstText = firstCell.getText();
                System.out.println("firstText = " + firstText);
                boolean isHaveRegex = false;
                //判断是否包含IF表达式
                Matcher ifMatcher = getPattern(if_placeholder).matcher(firstText);
                if (ifMatcher.find()) {
                    isHaveRegex = true;
                    String ifGroup = ifMatcher.group();
                    String key = ifGroup.substring(if_placeholder[0].length(), ifGroup.length() - if_placeholder[1].length()).trim();
                    String value = paramMap.get(key);
                    //判断是否显示表格
                    if (StringUtils.isBlank(value) || StringUtils.equals(value, "false")) {
                        deleteTable(table);
                        continue;
                    }
                }
                //判断是固定表还是动态表
                Matcher forMatcher = getPattern(for_placeholder).matcher(firstText);
                if (forMatcher.find()) {
                    isHaveRegex = true;
                    String forGroup = forMatcher.group();
                    String content = forGroup.substring(for_placeholder[0].length(), forGroup.length() - for_placeholder[1].length()).trim();
                    String key = content.split(";")[0];
                    int tempRowIndex = Integer.valueOf(content.split(";")[1]) - 1;
                    List dataList = listParam.get(key);
                    if (CollectionUtils.isNotEmpty(dataList)) {
                        for (int i = 0; i < dataList.size(); i++) {
                            Map<String, String> params = getMap(dataList.get(i));
                            params.put("index", (i + 1) + "");
                            copyReplace(table, table.getRow(tempRowIndex), tempRowIndex + i + 1, params);
                        }
                    }
                    //删除模板行
                    table.removeRow(tempRowIndex);
                }
                //替换表格剩余的所有占位符
                replaceTableParam(table, paramMap);
                //如果第一行存在表达式则删除表格第一行,否则按固定表替换变量
                if (isHaveRegex) {
                    table.removeRow(tableControlIndex);
                }
            }
            //生成文件
            File file = File.createTempFile(tempFilePrefix, tempFileSuffix);
            document.write(new FileOutputStream(file));
            return file;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("文件生成失败!");
        }

    }

    //参数转map类型
    public static Map<String,String> getMap(Object objParam){
        Map<String, String> paramMap = new HashMap<>();
        if (objParam instanceof Map) {
            paramMap = (Map<String, String>) objParam;
        } else {
            try {
                paramMap = BeanUtils.describe(objParam);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
        return paramMap;
    }

    /**
     * 删除表格
     * @param table 表格对象
     */
    private static void deleteTable(XWPFTable table) {
        List<XWPFTableRow> rows = table.getRows();
        int rowLength = rows.size();
        for (int i = 0; i < rowLength; i++) {
            table.removeRow(0);
        }
    }

    /**
     * 复制行并替换变量
     * @param table
     * @param sourceRow
     * @param rowIndex
     * @param params
     */
    private static void copyReplace(XWPFTable table, XWPFTableRow sourceRow, int rowIndex, Map<String, String> params) {
        //在表格指定位置新增一行
        XWPFTableRow targetRow = table.insertNewTableRow(rowIndex);
        //复制行属性
        targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
        List<XWPFTableCell> cellList = sourceRow.getTableCells();
        if (CollectionUtils.isEmpty(cellList)) {
            return;
        }
        //复制列及其属性和内容
        XWPFTableCell targetCell = null;
        for (XWPFTableCell sourceCell : cellList) {
            targetCell = targetRow.addNewTableCell();
            //列属性
            targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
            //段落属性
            if (sourceCell.getParagraphs() != null && sourceCell.getParagraphs().size() > 0) {
                targetCell.getParagraphs().get(0).getCTP().setPPr(sourceCell.getParagraphs().get(0).getCTP().getPPr());
                if (sourceCell.getParagraphs().get(0).getRuns() != null && sourceCell.getParagraphs().get(0).getRuns().size() > 0) {
                    XWPFRun cellR = targetCell.getParagraphs().get(0).createRun();
                    cellR.setText(sourceCell.getText());
                    cellR.setBold(sourceCell.getParagraphs().get(0).getRuns().get(0).isBold());
                } else {
                    targetCell.setText(sourceCell.getText());
                }
            } else {
                targetCell.setText(sourceCell.getText());
            }
        }
        //替换变量
        if (Objects.nonNull(params)) {
            for (XWPFTableCell cell : targetRow.getTableCells()) {
                String value = StringUtils.isNotBlank(cell.getText()) ? cell.getText().trim() : "";
                Matcher matcher = getPattern(param_placeholder).matcher(value);
                if (!matcher.find()) continue;
                //替换变量
                String key = value.substring(param_placeholder[0].length(), value.length() - param_placeholder[1].length()).trim();
                cell.removeParagraph(0);
                cell.setText(getValue(params, key));
            }
        }
    }

    /**
     * 替换表格变量
     * @param table
     * @param parametersMap
     */
    private static void replaceTableParam(XWPFTable table, Map<String, String> parametersMap) {
        for (int i = 0; i < table.getNumberOfRows(); i++) {
            XWPFTableRow row = table.getRow(i);
            List<XWPFTableCell> cells = row.getTableCells();
            for (XWPFTableCell cell : cells) {
                boolean isFind = false;
                String cellText = cell.getText();
                Matcher matcher = getPattern(param_placeholder).matcher(cellText);
                while (matcher.find()) {
                    String paramTemp = matcher.group();
                    String key = paramTemp.substring(param_placeholder[0].length(), paramTemp.length() - param_placeholder[1].length()).trim();
                    //替换变量
                    String value = getValue(parametersMap, key);
                    cellText = cellText.replace(paramTemp, value);

                    isFind = true;
                }
                if (isFind) {
                    cell.removeParagraph(0);
                    cell.setText(cellText);
                }
            }
        }
    }

    /**
     * 替换所有段落中的变量
     * @param document
     * @param parametersMap
     */
    private static void replaceParagraphs(XWPFDocument document, Map<String, String> parametersMap) {
        // 替换文本中的变量
        Iterator<XWPFParagraph> paragraphIte = document.getParagraphsIterator();
        while (paragraphIte.hasNext()) {
            XWPFParagraph paragraph = paragraphIte.next();
            replaceParagraphParam(paragraph, parametersMap);
        }
    }

    /**
     * 替换某个段落中的变量
     * @param xWPFParagraph
     * @param parametersMap
     */
    private static void replaceParagraphParam(XWPFParagraph xWPFParagraph, Map<String, String> parametersMap) {
        List<XWPFRun> runs = xWPFParagraph.getRuns();
        String xWPFParagraphText = xWPFParagraph.getText();

        Matcher matcher = getPattern(param_placeholder).matcher(xWPFParagraphText);
        if (!matcher.find()) return;
        //标签开始和结束run位置(注:getBeginRun 和 getEndRun 针对的是搜索内容分散在多个run的情况,本例因为是单个字符,所以只会在一个run中)
        int beginRunIndex = xWPFParagraph.searchText(param_placeholder[0], new PositionInParagraph()).getBeginRun();
        int endRunIndex = xWPFParagraph.searchText(param_placeholder[1], new PositionInParagraph()).getEndRun();

        // 在一个run标签内
        if (beginRunIndex == endRunIndex) {
            XWPFRun beginRun = runs.get(beginRunIndex);
            String beginRunText = beginRun.text();

            Matcher paramMatcher = getPattern(param_placeholder).matcher(beginRunText);
            if (paramMatcher.find()) {
                String group = paramMatcher.group();
                String key = group.substring(param_placeholder[0].length(), group.length() - param_placeholder[1].length()).trim();
                beginRunText = beginRunText.replace(group, getValue(parametersMap, key));
                //0 标识替换run的所有内容
                beginRun.setText(beginRunText, 0);
            }
        } else {
            //如果跨多个run标签,获取多个run的内容
            XWPFRun beginRun = runs.get(beginRunIndex);
            StringBuffer runSb = new StringBuffer();
            for (int i = beginRunIndex; i <= endRunIndex; i++) {
                XWPFRun run = runs.get(i);
                runSb.append(run.text());
            }
            //替换变量
            String runText = runSb.toString();
            Matcher paramMatcher = getPattern(param_placeholder).matcher(runText);
            if (paramMatcher.find()) {
                String group = paramMatcher.group();
                String key = group.substring(param_placeholder[0].length(), group.length() - param_placeholder[1].length()).trim();
                runText = runText.replace(group, getValue(parametersMap, key));
            }
            //创建一个新的run
            XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex + 1);
            insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
            insertNewRun.setText(runText);
            //删除原有的run
            for (int i = beginRunIndex; i <= endRunIndex; i++) {
                xWPFParagraph.removeRun(beginRunIndex);
            }

        }
        //递归方式替换所有的变量
        replaceParagraphParam(xWPFParagraph, parametersMap);
    }

    /**
     * 从map中取出对应key的值
     * @param parametersMap
     * @param key
     * @return
     */
    private static String getValue(Map<String, String> parametersMap, String key) {
        String value = parametersMap.get(key);
        if (StringUtils.isNotBlank(value) && !StringUtils.equals("null", value)) {
            if (StringUtils.equals("true", value)) {
                return "是";
            } else if (StringUtils.equals("false", value)) {
                return "否";
            }
            return value;
        }
        return "";
    }

}

测试数据:

package com.example.demo;

import com.example.demo.utils.WordUtil;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 测试类
 * @author 赏花归去
 * @Date 2019/8/26 17:34
 */
public class WordUtilTest extends DemoApplicationTests {

    public static void main(String[] args) {
        File file = WordUtil.generateWordFile(getParamMap(),getListMap(),"/template.docx");
        System.out.println("file.getPath() = " + file.getPath());
    }

    public static Map<String,String> getParamMap(){
        Map<String,String> paramMap = new HashMap<>();
        paramMap.put("title","赏花");
        paramMap.put("author","苏轼");
        paramMap.put("dynasty","北宋");
        paramMap.put("date","嘉祐二年");
        paramMap.put("isShow","true");
        return paramMap;
    }

    public static Map<String,List> getListMap(){
        Map<String,String> paramMap1 = new HashMap<>();
        paramMap1.put("name","将进酒");
        paramMap1.put("author","李白");
        Map<String,String> paramMap2 = new HashMap<>();
        paramMap2.put("name","侠客行");
        paramMap2.put("author","李白");

        Map<String,List> listMap = new HashMap<>();
        listMap.put("poetry",Arrays.asList(paramMap1,paramMap2));
        return listMap;
    }
}

 

结果

 

总结

虽然目前能够解决一些问题,但是作为一个工具类,还有很多不足之处,例如复杂表格的兼容性、复制行字体样式等,如果您有更好的idea,欢迎评论!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值