本文主要有如下幾個知識點:java
佔位符填充
頭像導出
表格填充
word轉pdf
先來看下最後的效果圖:linux
先來看下咱們的模板:apache
首先咱們須要先在word
模板裏面設置佔位符,這裏有一個很是重要的點就是咱們是根據${佔位符}來替換的,其實word
文檔本質上就是一個xml
文件,所以咱們須要保證佔位符不被切割,具體作法以下:windows
1.首先用解壓工具打開模板服務器
2.打開document.xml文件工具
3.能夠看出文件並未格式化,咱們先格式化代碼post
4.能夠看到咱們的佔位符被切割了,咱們須要幹掉中間多餘的。測試
5.點擊開始後直接覆蓋源文件就能夠了,如今能夠開始寫代碼了。字體
注意要保證咱們的每一個佔位符不被切割,不然是沒法進行替換的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; }
最後填充就是這個樣子:
最後在補充一個知識點,有時候會須要咱們把 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);
效果圖:
代碼寫到這裏也就結束了,大夥可根據業務需求自行進行調整,若是有什麼不對的地方請多多指教。