根據模板動態生成word(二)使用poi生成word

2023-07-10 21:00:39

@

一、準備模板

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這個開源專案使用方式吧,經過試驗這個確實功能完善,非常推薦。