基于EasyPoi实现循环写word段落

EasyPoi介绍

EasyPoi是一个Java的Excel和Word处理库,主要用于将Java对象转换为Excel或Word文档,并且可以从Excel或Word文档中读取数据到Java对象。本文将重点介绍如何使用EasyPoi写Word文档。

  1. 引入EasyPoi依赖
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-base</artifactId>
    <version>5.2.2</version>
</dependency>
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-word</artifactId>
    <version>5.2.2</version>
</dependency>
  1. 创建Word模板

可以使用Microsoft Word或其他能够创建Word模板的软件来设计Word文档的模板文件。模板文件中可以使用占位符来标记需要替换的内容。例如,在Word文档中可以插入占位符${name}表示需要替换的内容。

  1. 创建Java对象

使用Java对象来存放需要写入Word文档的数据。

@Data
public class User {
    private String name;
    private int age;
}
  1. 使用EasyPoi写入Word文档

创建一个WordExportUtil对象,利用模板和Java对象生成Word文档。

import cn.afterturn.easypoi.word.WordExportUtil;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Map;

public class WriteWordDemo {

    public static void main(String[] args) throws Exception {
        // 创建模板和数据对象
        User user = new User("Tom", 18);
        Map<String, Object> map = new HashMap<>();
        map.put("name", user.getName());
        map.put("age", user.getAge());

        // 加载模板文件
        XWPFDocument doc = WordExportUtil.exportWord07(
                "user_template.docx", map);

        // 输出文件
        FileOutputStream fos = new FileOutputStream("user.docx");
        doc.write(fos);
        fos.close();
        doc.close();
    }

}

使用WordExportUtil.exportWord07方法可以生成.docx格式的Word文档,第一个参数为Word模板文件名,第二个参数是一个Map对象,其中包含了模板中所有的占位符和对应的数据。

基于EasyPoi实现段落循环

EasyPoi写word模版导出比较方便。但是如果你希望在写word模版的时候将段落进行循环,那就实现不了。下面的方法基于EasyPoi实现了word段落的循环。

public class WordParagraphHolder {
    private final Logger logger = LoggerFactory.getLogger(WordParagraphHolder.class);
    private XWPFDocument sourceDocument;
    private String targetFileName;
    private Map<String, Object> data;

    public WordParagraphHolder(XWPFDocument sourceDocument, String targetFileName, Map<String, Object> data) {
        this.sourceDocument = sourceDocument;
        this.targetFileName = targetFileName;
        this.data = data;
    }

    public String execute() throws Exception {
        MyXWPFDocument second = null;
        FileOutputStream fos2 = null;
        try {

            // 循环生成段落
            Map<String, Object> data = this.data;
            this.parseAllParagraphic(sourceDocument.getParagraphs(), data);

            File targetFile = new File(targetFileName);
            FileOutputStream fos = FileUtils.openOutputStream(targetFile);

            sourceDocument.write(fos);
            // 关闭流

            fos.close();
            sourceDocument.close();

            // 循环段落赋值
            second = WordCache.getXWPFDocument(targetFile.getPath());
            this.evalAllParagraphic(second.getParagraphs(), data);

            fos2 = FileUtils.openOutputStream(targetFile);
            second.write(fos2);
        } catch (Exception e) {
            logger.error("生成word段落失败:", e);
            return "";
        } finally {
            // 关闭流
            fos2.close();
            second.close();
        }
        return targetFileName;
    }

    private void evalAllParagraphic(List<XWPFParagraph> paragraphs, Map<String, Object> map) throws Exception {
        int size = paragraphs.size();
        Map<String, Integer> indexMap = new HashMap<>();
        for (int i = 0; i < size; ++i) {
            XWPFParagraph paragraph = (XWPFParagraph) paragraphs.get(i);
            logger.info("段落内容:{}", paragraph.getText());
            if (paragraph.getText().indexOf("(") != -1) {
                Object obj = checkThisParagraphNeedIterator(paragraph, map);
                String listKey = getParagraphListKey(paragraph);
                Integer index = indexMap.getOrDefault(listKey, 0);
                if (Objects.nonNull(obj) && obj instanceof List && index < ((List) obj).size()) {
                    Object o = ((List) obj).get(index);
                    parseParagraph(paragraph, o);
                    index++;
                    indexMap.put(listKey, index);
                }
            }
        }
    }

    private void parseAllParagraphic(List<XWPFParagraph> paragraphs, Map<String, Object> map) throws Exception {
        int size = paragraphs.size();
        List<XWPFParagraph> paragraphList = new ArrayList<>();
        // 循环找出需要复制的段落
        for (int i = 0; i < size; ++i) {
            XWPFParagraph paragraph = (XWPFParagraph) paragraphs.get(i);
            if (paragraph.getText().indexOf("(") != -1) {
                Object obj = checkThisParagraphNeedIterator(paragraph, map);
                if (Objects.nonNull(obj) && obj instanceof List) {
                    paragraphList.add(paragraph);
                }
            }
        }
        // 复制段落
        for (XWPFParagraph paragraph : paragraphList) {
            System.out.println(paragraph.getText());
            Object obj = checkThisParagraphNeedIterator(paragraph, map);
            addParagraph(paragraph, (List) obj);
            // 删除模板段落
            XWPFDocument document = paragraph.getDocument();
            document.removeBodyElement(document.getPosOfParagraph(paragraph));
        }
    }

    /**
     * 复制段落
     *
     * @param source 原段落
     * @param doc
     */
    public XWPFParagraph createParagraph(XWPFParagraph source, XWPFDocument doc) {

        // 使用游标创建一个新行
        XmlCursor cursor = source.getCTP().newCursor();
        XWPFParagraph newParagraph = doc.insertNewParagraph(cursor);
        newParagraph.getCTP().set(source.getCTP().copy());

        return newParagraph;
    }

    private Object checkThisParagraphNeedIterator(XWPFParagraph paragraph, Map<String, Object> map) throws Exception {
        String text = paragraph.getText().trim();
        if (text != null && text.contains("fe:") && text.startsWith("(")) {
            text = text.replace("!fe:", "").replace("$fe:", "").replace("fe:", "").replace("(", "");
            String[] keys = text.replaceAll("\s{1,}", " ").trim().split(" ");
            Object result = PoiPublicUtil.getParamsValue(keys[0], map);
            return Objects.nonNull(result) ? result : new ArrayList(0);
        } else {
            return null;
        }
    }

    private String getParagraphListKey(XWPFParagraph paragraph) throws Exception {
        String text = paragraph.getText().trim();
        if (text != null && text.contains("fe:") && text.startsWith("(")) {
            text = text.replace("!fe:", "").replace("$fe:", "").replace("fe:", "").replace("(", "");
            String[] keys = text.replaceAll("\s{1,}", " ").trim().split(" ");
            return keys[0];
        } else {
            return null;
        }
    }

    /**
     * 赋值段落
     *
     * @param paragraph
     * @param obj
     * @throws Exception
     */
    public void parseParagraph(XWPFParagraph paragraph, Object obj) throws Exception {
        String listname = paragraph.getText().trim();
        boolean contains = listname.contains("fe:");
        if (!contains) {
            return;
        }
        Map<String, Object> objectMap = BeanUtil.beanToMap(obj);
        parseThisParagraph(paragraph, objectMap);
    }

    /**
     * 增加段落
     *
     * @param paragraph
     * @param list
     * @throws Exception
     */
    public void addParagraph(XWPFParagraph paragraph, List<Object> list) throws Exception {
        XWPFParagraph currentParagraph = paragraph;

        System.out.println("start for each data list :" + list.size());
        Iterator var11 = list.iterator();

        while (var11.hasNext()) {
            Object obj = var11.next();
            this.createParagraph(currentParagraph, currentParagraph.getDocument());
        }
    }

    /**
     * 遍历段落赋值
     *
     * @param paragraph
     * @param map
     * @throws Exception
     */
    private void parseThisParagraph(XWPFParagraph paragraph, Map<String, Object> map) throws Exception {
        XWPFRun currentRun = null;
        String currentText = "";
        Boolean isfinde = false;
        List<Integer> runIndex = new ArrayList();
        XWPFRun preRun = null;

        for (int i = 0; i < paragraph.getRuns().size(); ++i) {
            XWPFRun run = (XWPFRun) paragraph.getRuns().get(i);
            String text = run.getText(0);
            if (!StringUtils.isEmpty(text)) {
                if (isfinde) {
                    currentText = currentText + text;
                    if (currentText.indexOf("[") == -1) {
                        isfinde = false;
                        runIndex.clear();
                    } else {
                        runIndex.add(i);
                    }

                    if (currentText.indexOf("]") != -1) {
                        this.changeValues(paragraph, currentRun, currentText, runIndex, map);
                        currentText = "";
                        isfinde = false;
                    }
                } else if (text.indexOf("[") >= 0) {
                    currentText = text;
                    isfinde = true;
                    currentRun = run;
                } else {
                    currentText = "";
                }

                if (currentText.indexOf("]") != -1) {
                    this.changeValues(paragraph, currentRun, currentText, runIndex, map);
                    isfinde = false;
                }
            }

            // 去除多余的字符串
            if (!StringUtils.isEmpty(text)) {
                if (text.indexOf("(") != -1) {
                    preRun = run;
                } else if (text.indexOf("$fe") != -1) {
                    run.setText("", 0);
                    // 清除第一个(
                    preRun.setText("", 0);
                }
                // 清除最后一个)
                if (i == paragraph.getRuns().size() - 1) {
                    if (text.indexOf(")") != -1) {
                        if (text.length() >= 1) {
                            String replace = text.replace(")", "");
                            run.setText(replace, 0);
                        }
                    }
                }
            }
        }
    }

    private void changeValues(XWPFParagraph paragraph, XWPFRun currentRun, String currentText, List<Integer> runIndex, Map<String, Object> map) throws Exception {
        Object obj = getRealValue(currentText, map);
        if (obj instanceof ImageEntity) {
            currentRun.setText("", 0);
            ExcelMapParse.addAnImage((ImageEntity) obj, currentRun);
        } else {
            currentText = obj.toString();
            PoiPublicUtil.setWordText(currentRun, currentText);
        }

        for (int k = 0; k < runIndex.size(); ++k) {
            ((XWPFRun) paragraph.getRuns().get((Integer) runIndex.get(k))).setText("", 0);
        }

        runIndex.clear();
    }

    public static Object getRealValue(String currentText, Map<String, Object> map) throws Exception {
        String params = "";

        while (currentText.indexOf("[") != -1) {
            params = currentText.substring(currentText.indexOf("[") + 1, currentText.indexOf("]"));
            Object obj = PoiElUtil.eval(params.trim(), map);
            if (obj instanceof ImageEntity || obj instanceof List || obj instanceof ExcelListEntity) {
                return obj;
            }

            if (obj != null) {
                currentText = currentText.replace("[" + params + "]", obj.toString());
            } else {
                currentText = currentText.replace("[" + params + "]", "");
            }
        }

        return currentText;
    }

使用方法
新建word插入如下内容:

($fe:resultList [createDate],我使用[number]元,购买苹果[amount]个,截止日期[endDate],使用支付方式[type])
在这里插入图片描述

其中resultList 为入参list的名称,可以自由更换,[createDate]内createDate为list中字段的名称,其他的为固定格式。

执行下面的测试代码:

 public static void main(String[] args) throws Exception {
        Map<String, Object> date = createDate();
        String sourceFile = "d:/temp/模版word.docx";
        String targetFile = "d:/temp/输出结果.docx";
        MyXWPFDocument first = WordCache.getXWPFDocument(sourceFile);

        WordParagraphHolder test = new WordParagraphHolder(first, targetFile, date);
        test.execute();
    }
    private static Map<String, Object> createDate() {

        //填充数据
        List<WordExportBatch> resultList = new ArrayList<>();
        WordExportBatch wordExport = new WordExportBatch();
        WordExportBatch wordExport1 = new WordExportBatch();
        wordExport.setCreateDate("2022/9/30");
        wordExport1.setCreateDate("2022/9/28");
        wordExport.setNumber("11");
        wordExport1.setNumber("15");
        wordExport.setAmount("1234.5");
        wordExport1.setAmount("2345.77");
        wordExport.setEndDate("2022/12/31");
        wordExport1.setEndDate("2022/11/30");
        wordExport.setType("支付宝");
        wordExport1.setType("微信");
        resultList.add(wordExport);
        resultList.add(wordExport1);
        //准备数据
        Map<String, Object> params = new HashMap<>();
        params.put("resultList", resultList);

        return params;
    }

输出:
在这里插入图片描述