Java填充word模板

前言

本文主要有如下幾個知識點:java

  • 佔位符填充
  • 頭像導出
  • 表格填充
  • word轉pdf

先來看下最後的效果圖:linux

image.png

正文

模板配置

先來看下咱們的模板:apache

image.png

首先咱們須要先在word模板裏面設置佔位符,這裏有一個很是重要的點就是咱們是根據${佔位符}來替換的,其實word文檔本質上就是一個xml文件,所以咱們須要保證佔位符不被切割,具體作法以下:windows

1.首先用解壓工具打開模板服務器

image.png

2.打開document.xml文件工具

image.png

3.能夠看出文件並未格式化,咱們先格式化代碼post

image.png

image.png

4.能夠看到咱們的佔位符被切割了,咱們須要幹掉中間多餘的。測試

image.png

image.png

5.點擊開始後直接覆蓋源文件就能夠了,如今能夠開始寫代碼了。字體

image.png

注意要保證咱們的每一個佔位符不被切割,不然是沒法進行替換的ui

模板填充

導入jar

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

首先看下咱們兩個表格的實體類(不必定要給表格建對象,根據我的需求添加便可)

// 家庭成員
@Builder(toBuilder = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EpRewandpun {
    private String urewdate;
    private String urewunit;
    private String urewdesc;
}

// 獎懲狀況
@Builder(toBuilder = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EpPmenber {
    private String uconnection;
    private String uname;
    private String ubirthday;
    private String uworkunit;
    private String uploity;
    private String ustatus;
}

接着看下咱們的測試方法:

@SpringBootTest
class Tests {
    @Test
    void contextLoads() throws IOException {
        String template = "C:UserslikunDesktop員工基本狀況表.docx";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("${uname}", "烏龜");
        paramMap.put("${usex}", "男");
        paramMap.put("${ubirthdate}", "1998年10月22日");
        paramMap.put("${unation}", "漢族");
        paramMap.put("${unative}", "廣東深圳");
        paramMap.put("${uplace}", "廣東汕頭");
        paramMap.put("${upolity}", "團員");
        paramMap.put("${uworkdate}", "2020年3月16日");
        paramMap.put("${uhealth}", "良好");
        paramMap.put("${umajorpost}", "軟件開發");
        paramMap.put("${umajor}", "Java開發");
        
        // 照片路徑以及大小
        Map<String, Object> phomap = new HashMap<>(8);
        phomap.put("width", 100);
        phomap.put("height", 130);
        phomap.put("type", "png");
        phomap.put("content", "E:\\Photo\\頭像.jpg");
        paramMap.put("${upho}", phomap);
        
        //查詢員工家庭信息
        List<EpPmenber> menberlist = new ArrayList<>();
        for (int i = 1; i < 3; i++) {
            EpPmenber pmenber = new EpPmenber();
            pmenber.setUname("小王");
            pmenber.setUconnection("父親");
            pmenber.setUbirthday("1962年10月2日");
            pmenber.setUploity("羣衆");
            pmenber.setUworkunit("廣東xxx公司");
            pmenber.setUstatus("無");
            menberlist.add(pmenber);
        }
        paramMap.put("menberlist", menberlist);
        
        //查詢員工獎勵狀況
        List<EpRewandpun> andpunlist = new ArrayList<>();
        for (int i = 1; i < 3; i++) {
            EpRewandpun rewandpun = new EpRewandpun();
            rewandpun.setUrewdate("2020年5月1日");
            rewandpun.setUrewunit("深圳xxx有限公司");
            rewandpun.setUrewdesc("無");
            andpunlist.add(rewandpun);
        }
        paramMap.put("andpunlist", andpunlist);
        
        // 模板填充
        XWPFDocument doc = WordUtil.generateWord(paramMap, template);
        FileOutputStream fopts = new FileOutputStream("C:Users\\likun\\Desktop\\模板填充.docx");
        doc.write(fopts);
        fopts.close();
    }
}

代碼比較簡單再也不細說,先來看下咱們的 generateWord 核心方法:

public class WordUtil {

    /**
     * 根據指定的參數值、模板,生成 word 文檔
     * 注意:其它模板須要根據狀況進行調整
     *
     * @param param    變量集合
     * @param template 模板路徑
     */
    public static XWPFDocument generateWord(Map<String, Object> param, String template) {
        XWPFDocument doc = null;
        try {
            OPCPackage pack = POIXMLDocument.openPackage(template);
            doc = new XWPFDocument(pack);
            if (param != null && param.size() > 0) {
                // 處理段落
                List<XWPFParagraph> paragraphList = doc.getParagraphs();
                processParagraphs(paragraphList, param, doc);
                // 處理表格
                Iterator<XWPFTable> it = doc.getTablesIterator();
                //表格索引
                int i = 0;
                List<EpPmenber> menberlist = (List<EpPmenber>) param.get("menberlist");
                List<EpRewandpun> andpunlist = (List<EpRewandpun>) param.get("andpunlist");

                while (it.hasNext()) {
                    XWPFTable table = it.next();
                    int size = table.getRows().size() - 1;
                    XWPFTableRow row2 = table.getRow(size);
                    if (i == 1) {//家庭成員
                        if (menberlist.size() > 0) {
                            for (int j = 0; j < menberlist.size() - 1; j++) {
                                copy(table, row2, size + j);
                            }
                        }
                    } else if (i == 2) {//獎懲狀況
                        if (andpunlist.size() > 0) {
                            for (int j = 0; j < andpunlist.size() - 1; j++) {
                                copy(table, row2, size + j);
                            }
                        }
                     }
                        _row++;
                    }
                    i++;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return doc;
    }
 }

看下咱們的 copy 方法,用來拷貝行屬性進行對錶格進行復制

/**
 * 拷貝賦值行
 *
 */
public static void copy(XWPFTable table, XWPFTableRow sourceRow, int rowIndex) {
    // 在表格指定位置新增一行
    XWPFTableRow targetRow = table.insertNewTableRow(rowIndex);
    // 複製行屬性
    targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
    List<XWPFTableCell> cellList = sourceRow.getTableCells();
    if (null == 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());
        }
    }
}

接下來是咱們的 processParagraphs 方法,用來進行佔位符的替換以及頭像的插入。

/**
 * 處理段落
 */
@SuppressWarnings({"unused", "rawtypes"})
public static void processParagraphs(List<XWPFParagraph> paragraphList, Map<String, Object> param, XWPFDocument doc) throws InvalidFormatException, IOException {
    if (paragraphList != null && paragraphList.size() > 0) {
        for (XWPFParagraph paragraph : paragraphList) {
            List<XWPFRun> runs = paragraph.getRuns();
            for (XWPFRun run : runs) {
                String text = run.getText(0);
                if (text != null) {
                    boolean isSetText = false;
                    for (Entry<String, Object> entry : param.entrySet()) {
                        String key = entry.getKey();
                        if (text.contains(key)) {
                            isSetText = true;
                            Object value;
                            if (entry.getValue() != null) {
                                value = entry.getValue();
                            } else {
                                value = "";
                            }
                            // 文本替換
                          if (value instanceof String) {
                            // 處理答案中的回車換行
                          if (((String) value).contains("n")) {
                                    String[] lines = ((String) value).split("n");
                                    if (lines.length > 0) {
                                        text = text.replace(key, lines[0]);
                                        for (int j = 1; j < lines.length; j++) {
                                            run.addCarriageReturn();
                                            run.setText(lines[j]);
                                        }
                                    }
                                } else {
                                    text = text.replace(key, value.toString());
                                }
                            } else if (value instanceof Map) {
                                // 圖片替換
                                text = text.replace(key, "");
                                Map pic = (Map) value;
                                int width = Integer.parseInt(pic.get("width").toString());
                                int height = Integer.parseInt(pic.get("height").toString());
                                int picType = getPictureType(pic.get("type").toString());
                                String byteArray = (String) pic.get("content");
                                CTInline inline = run.getCTR().addNewDrawing().addNewInline();
                                //插入圖片
                                insertPicture(doc, byteArray, inline, width, height, paragraph);
                            }
                        }
                    }
                    if (isSetText) {
                        run.setText(text, 0);
                    }
                }
            }
        }
    }
}

接下來是 insertPicture 方法,用來插入圖片,以及 getPictureType 方法獲取圖片類型。

/**
 * 插入圖片
 *
 */
 private static void insertPicture(XWPFDocument document, String filePath, CTInline inline, int width, int height, XWPFParagraph paragraph) throws Exception {
        // 讀取圖片路徑
        InputStream inputStream = new FileInputStream(new File(filePath));;
        
        document.addPictureData(inputStream, XWPFDocument.PICTURE_TYPE_PNG);
        int id = document.getAllPictures().size() - 1;
        final int emu = 9525;
        width *= emu;
        height *= emu;
        String blipId = paragraph.getDocument().getRelationId(document.getAllPictures().get(id));

        String picXml = getPicXml(blipId, width, height);
        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("IMG_" + id);
        docPr.setDescr("IMG_" + id);
    }
    
    
/**
 * 根據圖片類型,取得對應的圖片類型代碼
 *
 * @param picType
 * @return int
 */
 private static int getPictureType(String picType) {
    int res = XWPFDocument.PICTURE_TYPE_PICT;
    if (picType != null) {
        if ("png".equalsIgnoreCase(picType)) {
            res = XWPFDocument.PICTURE_TYPE_PNG;
        } else if ("dib".equalsIgnoreCase(picType)) {
            res = XWPFDocument.PICTURE_TYPE_DIB;
        } else if ("emf".equalsIgnoreCase(picType)) {
            res = XWPFDocument.PICTURE_TYPE_EMF;
        } else if ("jpg".equalsIgnoreCase(picType) || "jpeg".equalsIgnoreCase(picType)) {
            res = XWPFDocument.PICTURE_TYPE_JPEG;
        } else if ("wmf".equalsIgnoreCase(picType)) {
            res = XWPFDocument.PICTURE_TYPE_WMF;
        }
    }
    return res;
}

最後填充就是這個樣子:

image.png

word轉pdf

最後在補充一個知識點,有時候會須要咱們把 word 文檔轉成 pdf,其實網上是有一個收費插件 `aspose-words,具體如何破解請上網搜查,首先導入包:

<!-- Word文檔轉換 -->
<dependency>
 <groupId>com.aspose.words</groupId>
 <artifactId>aspose-words-jdk16</artifactId>
 <version>16.4.0</version>
</dependency>

接下來看下代碼:

public class AsposeWordUtil {

   private static final String WIN = "win";
   
/**
 * word轉pdf 需引入 aspose-words-16.4.0-jdk16.jar包 收費插件windows linux下都可用
 *
 * @param inPath
 * 源文件路徑
 * @param outPath
 * 輸出文件路徑
 */
 public static void convertPdfToDocx(String inPath, String outPath) {
      try {
         FontSettings fontSettings = new FontSettings();
         File file = new File(outPath);
         FileOutputStream os = new FileOutputStream(file);

         Document doc = new Document(inPath); 
         // 另外服務器須要上傳中文字體到/usr/share/fonts目錄(複製windowsC:WindowsFonts目錄下的字體文件便可)
         String cos = System.getProperty("os.name");
         if (cos.toLowerCase().startsWith(WIN)) {
            // windows環境
            fontSettings.setFontsFolder("C:/Windows/Fonts", false);
         } else {
            // Linux環境
            fontSettings.setFontsFolder("/usr/share/fonts", false);
         }
         doc.setFontSettings(fontSettings);
         // 全面支持DOC, DOCX, OOXML, RTF HTML,OpenDocument, PDF,EPUB, XPS, SWF 相互轉換
         doc.save(os, SaveFormat.PDF);
         os.close();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

注意:Linux服務器上須要上傳中文字體,用法也很簡單,只要傳入word文檔路徑和要生成的pdf路徑就能夠了。

String wordPath = "C:Users\\likun\\Desktop\\人員基本狀況.docx";
String pdfPath = "C:Users\\likun\\Desktop\\人員基本狀況.pdf";
AsposeWordUtil.convertPdfToDocx(wordPath, pdfPath);

效果圖:

image.png

總結

代碼寫到這裏也就結束了,大夥可根據業務需求自行進行調整,若是有什麼不對的地方請多多指教。

image

相關文章
相關標籤/搜索