@

一、准备模板

1、创建模板文件

创建一个word文件,输入如下图所示的内容:
请添加图片描述

二、代码实践

1、引入依赖

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

2、自定义XWPFDocument

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlToken;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;

import java.io.IOException;
import java.io.InputStream;

public class CustomXWPFDocument extends XWPFDocument {
    public CustomXWPFDocument(InputStream in) throws IOException {
        super(in);
    }

    public CustomXWPFDocument() {
        super();
    }

    public CustomXWPFDocument(OPCPackage pkg) throws IOException {
        super(pkg);
    }

    /**
     * @param id
     * @param width 宽
     * @param height 高
     * @param paragraph 段落
     */
    public void createPicture(int id, int width, int height,
                              XWPFParagraph paragraph) {
        final int EMU = 9525;
        width *= EMU;
        height *= EMU;
        String blipId = super.getRelationId(super.getAllPictures().get(id));
        CTInline inline = paragraph.createRun().getCTR().addNewDrawing()
                .addNewInline();
        String picXml = ""
                + "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"
                + "   <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
                + "      <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
                + "         <pic:nvPicPr>" + "            <pic:cNvPr id=\""
                + id
                + "\" name=\"Generated\"/>"
                + "            <pic:cNvPicPr/>"
                + "         </pic:nvPicPr>"
                + "         <pic:blipFill>"
                + "            <a:blip r:embed=\""
                + blipId
                + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>"
                + "            <a:stretch>"
                + "               <a:fillRect/>"
                + "            </a:stretch>"
                + "         </pic:blipFill>"
                + "         <pic:spPr>"
                + "            <a:xfrm>"
                + "               <a:off x=\"0\" y=\"0\"/>"
                + "               <a:ext cx=\""
                + width
                + "\" cy=\""
                + height
                + "\"/>"
                + "            </a:xfrm>"
                + "            <a:prstGeom prst=\"rect\">"
                + "               <a:avLst/>"
                + "            </a:prstGeom>"
                + "         </pic:spPr>"
                + "      </pic:pic>"
                + "   </a:graphicData>" + "</a:graphic>";

        inline.addNewGraphic().addNewGraphicData();
        XmlToken xmlToken = null;
        try {
            xmlToken = XmlToken.Factory.parse(picXml);
        } catch (XmlException xe) {
            xe.printStackTrace();
        }
        inline.set(xmlToken);

        inline.setDistT(0);
        inline.setDistB(0);
        inline.setDistL(0);
        inline.setDistR(0);

        CTPositiveSize2D extent = inline.addNewExtent();
        extent.setCx(width);
        extent.setCy(height);

        CTNonVisualDrawingProps docPr = inline.addNewDocPr();
        docPr.setId(id);
        docPr.setName("图片名称");
        docPr.setDescr("描述信息");
    }
}

2、公用的方法和变量

	private final String REGEX = "\\$\\{(.+?)\\}";

    private CustomXWPFDocument document;

    public PoICreateWordFactory(String templatePath) throws IOException {
        loadTemplate(templatePath);
    }

    /**
     * 加载模板
     *
     * @param templatePath 模板路径
     * @return 包含返回true, 不包含返回false
     */
    private void loadTemplate(String templatePath) throws IOException {
        try (InputStream in = Files.newInputStream(Paths.get(templatePath))) {
            //转成word
            this.document = new CustomXWPFDocument(in);
        }
    }

    /**
     * 生成word
     *
     * @param targetFile word生成路径
     * @return 包含返回true, 不包含返回false
     */
    public void createWordFile(String targetFile) throws IOException {
        try (FileOutputStream out = new FileOutputStream(targetFile)){
            document.write(out);
        }
    }

    /**
     * 判断文本中是否包含$
     *
     * @param text 文本
     * @return 包含返回true, 不包含返回false
     */
    public boolean checkText(String text) {
        boolean check = false;
        if (text.indexOf("$") != -1) {
            check = true;
        }
        return check;
    }

3、工具类引用的包名

import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

4、段落文本替换

	/**
     * 替换段落文本
     *
     * @param textMap(数据源)
     */
    public void replaceText(Map<String, Object> textMap) {
        //获取段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    Pattern pattern = Pattern.compile(REGEX);
                    Matcher matcher = pattern.matcher(run.toString());
                    if (matcher.find()) {
                        String key = matcher.group(1);
                        if(textMap.containsKey(key)){
                            run.setText(String.valueOf(textMap.get(key)), 0);
                        }
                    }
                }
            }
        }
    }

5、图片替换

	/**
     * 替换图片
     *
     * @param imageMap(数据源)
     */
    public void replaceImage(Map<String, byte[]> imageMap) throws org.apache.poi.openxml4j.exceptions.InvalidFormatException {
        //段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        Set<Map.Entry<String, byte[]>> imageSets = imageMap.entrySet();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    Pattern pattern = Pattern.compile(REGEX);
                    String runText = run.toString();
                    Matcher matcher = pattern.matcher(runText);
                    if (matcher.find()) {
                        String key = matcher.group(1);
                        if(imageMap.containsKey(key)){
                            //清空原有内容
                            run.setText("", 0);
                            //设置图片
                            document.addPictureData(imageMap.get(key), XWPFDocument.PICTURE_TYPE_PNG);
                            //创建一个word图片,并插入到文档中-->像素可改
                            document.createPicture(document.getAllPictures().size() - 1, 240, 240,paragraph);
                            break;
                        }
                    }
                }
            }
        }
    }

6、表格替换

	/**
     * 替换表格内容
     *
     * @param index(表格索引:第几个表格)
     * @param tableList(数据源)
     * @Return void
     * @Exception
     */
    public void replaceTable(int index, List<List<String>> tableList) {
        XWPFTable table = document.getTables().get(index);
        //创建行,根据需要插入的数据添加新行,不处理表头
        for (int i = 1; i <= tableList.size(); i++) {
            table.createRow();
        }
        //遍历表格插入数据
        List<XWPFTableRow> rows = table.getRows();
        for (int i = 1; i < tableList.size()+1; i++) {
            XWPFTableRow newRow = table.getRow(i);
            List<XWPFTableCell> cells = newRow.getTableCells();
            List<String> rowData = tableList.get(i - 1);
            for (int j = 0; j < rowData.size(); j++) {
                XWPFTableCell cell = cells.get(j);
                String text = rowData.get(j);
                cell.setText(text);
                //表格样式一致-->没有此段表格会默认左对齐
                //有此段会使表格格式一致
                CTTc cttc = cell.getCTTc();
                CTTcPr ctPr = cttc.addNewTcPr();
                ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
                cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
            }
        }
    }

7、完整的工具类代码

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PoICreateWordFactory {
    

    /**
     * 生成ord
     *
     */
    public  void crateWord(Map<String, Object> dataMap, String templatePath, String targetFile) throws IOException, InvalidFormatException {
        //加载模板文件
        loadTemplate(templatePath);

        //将dataMap拆分成 textMap、imageMap、tableMap TODO 烂尾中

        //段落替换变量
        Map<String, Object> textMap = new HashMap<>();
        //替换模板数据
        replaceText(textMap);

        //图片替换变量
        Map<String, byte[]> imageMap = new HashMap<>();
        replaceImage(imageMap);

        //写入表格
        List<List<String>> arrearsList = new ArrayList<>();
        replaceTable(0, arrearsList);

        //生成新的word
        createWordFile(targetFile);
    }

    private final String REGEX = "\\$\\{(.+?)\\}";

    private CustomXWPFDocument document;

    public PoICreateWordFactory() {}

    public PoICreateWordFactory(String templatePath) throws IOException {
        loadTemplate(templatePath);
    }

    /**
     * 加载模板
     *
     * @param templatePath 模板路径
     * @return 包含返回true, 不包含返回false
     */
        public void loadTemplate(String templatePath) throws IOException {
        try (InputStream in = Files.newInputStream(Paths.get(templatePath))) {
            //转成word
            this.document = new CustomXWPFDocument(in);
        }
    }

    /**
     * 生成word
     *
     * @param targetFile word生成路径
     * @return 包含返回true, 不包含返回false
     */
    public void createWordFile(String targetFile) throws IOException {
        try (FileOutputStream out = new FileOutputStream(targetFile)){
            document.write(out);
        }
    }

    /**
     * 判断文本中是否包含$
     *
     * @param text 文本
     * @return 包含返回true, 不包含返回false
     */
    public boolean checkText(String text) {
        boolean check = false;
        if (text.indexOf("$") != -1) {
            check = true;
        }
        return check;
    }


    /**
     * 替换段落文本
     *
     * @param textMap(数据源)
     */
    public void replaceText(Map<String, Object> textMap) {
        //获取段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    Pattern pattern = Pattern.compile(REGEX);
                    Matcher matcher = pattern.matcher(run.toString());
                    if (matcher.find()) {
                        String key = matcher.group(1);
                        if(textMap.containsKey(key)){
                            run.setText(String.valueOf(textMap.get(key)), 0);
                        }
                    }
                }
            }
        }
    }

    /**
     * 替换图片
     *
     * @param imageMap(数据源)
     */
    public void replaceImage(Map<String, byte[]> imageMap) throws org.apache.poi.openxml4j.exceptions.InvalidFormatException {
        //段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        Set<Map.Entry<String, byte[]>> imageSets = imageMap.entrySet();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    Pattern pattern = Pattern.compile(REGEX);
                    String runText = run.toString();
                    Matcher matcher = pattern.matcher(runText);
                    if (matcher.find()) {
                        String key = matcher.group(1);
                        if(imageMap.containsKey(key)){
                            //清空原有内容
                            run.setText("", 0);
                            //设置图片
                            document.addPictureData(imageMap.get(key), XWPFDocument.PICTURE_TYPE_PNG);
                            //创建一个word图片,并插入到文档中-->像素可改
                            document.createPicture(document.getAllPictures().size() - 1, 240, 240,paragraph);
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * 替换表格内容
     *
     * @param index(表格索引:第几个表格)
     * @param tableList(数据源)
     * @Return void
     * @Exception
     */
    public void replaceTable(int index, List<List<String>> tableList) {
        XWPFTable table = document.getTables().get(index);
        //创建行,根据需要插入的数据添加新行,不处理表头
        for (int i = 1; i <= tableList.size(); i++) {
            table.createRow();
        }
        //遍历表格插入数据
        List<XWPFTableRow> rows = table.getRows();
        for (int i = 1; i < tableList.size()+1; i++) {
            XWPFTableRow newRow = table.getRow(i);
            List<XWPFTableCell> cells = newRow.getTableCells();
            List<String> rowData = tableList.get(i - 1);
            for (int j = 0; j < rowData.size(); j++) {
                XWPFTableCell cell = cells.get(j);
                String text = rowData.get(j);
                cell.setText(text);
                //表格样式一致-->没有此段表格会默认左对齐
                //有此段会使表格格式一致
                CTTc cttc = cell.getCTTc();
                CTTcPr ctPr = cttc.addNewTcPr();
                ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
                cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
            }
        }
    }
}

三、验证模板生成

1、测试代码

public static void main(String[] args) throws IOException, InvalidFormatException {
        String templatePath = "D:\\文章\\word生成\\poi\\qiantiao.docx";
        String targetFile = "D:\\test\\qiantiao.docx";
        //初始化,并加载模板文件
        PoICreateWordFactory poICreateWordFactory = new PoICreateWordFactory(templatePath);

        //段落替换变量
        LocalDate currentDate = LocalDate.now();
        LocalDate endDate = currentDate.plusYears(1L);
        Map<String, Object> textMap = new HashMap<>();
        textMap.put("debtor", "陈有楚");
        textMap.put("nowYear", String.valueOf(currentDate.getYear()));
        textMap.put("nowMonth", String.valueOf(currentDate.getMonthValue()));
        textMap.put("nowDay", String.valueOf(currentDate.getDayOfMonth()));
        textMap.put("arrears", "一顿老魏、贵州大黄牛、v我50");
        textMap.put("endYear", String.valueOf(endDate.getYear()));
        textMap.put("endMonth", String.valueOf(endDate.getMonthValue()));
        textMap.put("endDay", String.valueOf(endDate.getDayOfMonth()));
        textMap.put("creditor", "知北游");
        //替换模板数据
        poICreateWordFactory.replaceText(textMap);

        //图片替换变量
        FileInputStream imageInput = new FileInputStream("D:\\picture\\其他\\24-05-23-142418.png");
        byte[] bytes = new byte[imageInput.available()];
        imageInput.read(bytes);
        imageInput.close();
        Map<String, byte[]> imageMap = new HashMap<>();
        imageMap.put("image1", bytes);
        poICreateWordFactory.replaceImage(imageMap);

        //写入表格
        List<List<String>> arrearsList = new ArrayList<>();
        arrearsList.add(Arrays.asList("一顿老魏", "1", "三月内"));
        arrearsList.add(Arrays.asList("贵州大黄牛", "1", "一年内"));
        arrearsList.add(Arrays.asList("v我50", "1", "一月内"));
        //获取表格位置 0代表第一个表格,写死第一个,模板里也只有一个模板
        poICreateWordFactory.replaceTable(0, arrearsList);

        //生成新的word
        poICreateWordFactory.createWordFile(targetFile);

    }

2、生成效果

请添加图片描述

四、总结

其实从测试代码里就可以发现这其实只是一个半成品代码,文本替换、图片替换、表格替换甚至需要分别传递不同的数据map。本来是打算合并成一个dataMap,然后根据参数类来区分是文本、图片、表格的。然后拆分成多个数据map。但是在写这些代码时发现了也是基于poi开发的开源项目poi-tl。功能很全,我想实现的功能他都有,顿时我写的上面这些代码就失去了意义,然后就烂尾了。。。后面有时间介绍一下poi-tl这个开源项目使用方式吧,经过试验这个确实功能完善,非常推荐。