轉(小改):
Java利用poi生成word(包含插入圖片,動態表格,行合並)
測試模板樣式:
Word生成結果:
圖表 2
需要的jar包:(具體jar可自行去maven下載)
注意:需要嚴格按照上面版本下載jar包,否則可能出現jar包之間不能匹配的導致代碼報錯
各種 jar包都可以在這里下載:
Test測試類:
package p1; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class Main { public static void main(String[] args) throws Exception { //需要進行文本替換的信息 Map<String, Object> data = new HashMap<String, Object>(); data.put("${date}", "2018-03-06"); data.put("${name}", "東方明珠"); data.put("${address}", "華東院"); /* data.put("${communityvalue}", ""); data.put("${safetycode}", "華東院"); data.put("${picture2}", ""); data.put("${picture3}", "");*/ data.put("${buildingvalue2}", "華東院"); /* data.put("${patrolPhoto1}", ""); data.put("${patrolPhoto2}", ""); data.put("${buildingvalue3}", "中國");*/ //圖片,如果是多個圖片,就新建多個map Map<String,Object> picture1 = new HashMap<String, Object>(); picture1.put("width", 100); picture1.put("height", 150); picture1.put("type", "jpg"); picture1.put("content", WorderToNewWordUtils.inputStream2ByteArray(new FileInputStream("D:/docTest/p1.jpg"), true)); data.put("${picture1}",picture1); //需要進行動態生成的信息 List<Object> mapList = new ArrayList<Object>(); //第一個動態生成的數據列表 List<String[]> list01 = new ArrayList<String[]>(); list01.add(new String[]{"A","11111111111","22","22"}); list01.add(new String[]{"A","22222222222","33","22"}); list01.add(new String[]{"B","33333333333","44","22"}); list01.add(new String[]{"C","44444444444","55","22"}); //第二個動態生成的數據列表 List<String[]> list02 = new ArrayList<String[]>(); list02.add(new String[]{"A","11111111111","22","22"}); list02.add(new String[]{"d","22222222222","33","22"}); list02.add(new String[]{"B","33333333333","44","22"}); list02.add(new String[]{"C","44444444444","55","22"}); mapList.add(list01); mapList.add(list02); //需要動態改變表格的位置;第一個表格的位置為0 int[] placeList = {1,4}; CustomXWPFDocument doc = WorderToNewWordUtils.changWord("D:/docTest/t1.docx",data,mapList,placeList); FileOutputStream fopts = new FileOutputStream("D:/呵呵.docx"); doc.write(fopts); fopts.close(); } }
WorderToNewWordUtils類:
package p1; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.poi.POIXMLDocument; import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.XWPFTable; import org.apache.poi.xwpf.usermodel.XWPFTableCell; import org.apache.poi.xwpf.usermodel.XWPFTableRow; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge; /** * Created by 王景偉 on 2018/12/19. */ public class WorderToNewWordUtils { /** * 根據模板生成word文檔 * @param inputUrl 模板路徑 * @param textMap 需要替換的文本內容 * @param mapList 需要動態生成的內容 * @return */ public static CustomXWPFDocument changWord(String inputUrl, Map<String, Object> textMap, List<Object> mapList,int[] placeList) { CustomXWPFDocument document = null; try { //獲取docx解析對象 document = new CustomXWPFDocument(POIXMLDocument.openPackage(inputUrl)); //解析替換文本段落對象 WorderToNewWordUtils.changeText(document, textMap); //解析替換表格對象 WorderToNewWordUtils.changeTable(document, textMap, mapList,placeList); } catch (IOException e) { e.printStackTrace(); } return document; } /** * 替換段落文本 * @param document docx解析對象 * @param textMap 需要替換的信息集合 */ public static void changeText(CustomXWPFDocument document, 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) { //替換模板原來位置 Object ob = changeValue(run.toString(), textMap); System.out.println("段落:"+run.toString()); if (ob instanceof String){ run.setText((String)ob,0); } } } } } /** * 替換表格對象方法 * @param document docx解析對象 * @param textMap 需要替換的信息集合 * @param mapList 需要動態生成的內容 */ public static void changeTable(CustomXWPFDocument document, Map<String, Object> textMap, List<Object> mapList,int[] placeList){ //獲取表格對象集合 List<XWPFTable> tables = document.getTables(); //循環所有需要進行替換的文本,進行替換 for (int i = 0; i < tables.size(); i++) { XWPFTable table = tables.get(i); if(checkText(table.getText())){ List<XWPFTableRow> rows = table.getRows(); System.out.println("簡單表格替換:"+rows); //遍歷表格,並替換模板 eachTable(document,rows, textMap); } } int index=0; //操作word中的表格 for (int i = 0; i < tables.size(); i++) { //只處理行數大於等於2的表格,且不循環表頭 XWPFTable table = tables.get(i); if(placeList[index]==i){ List<String[]> list = (List<String[]>) mapList.get(index); //第二個表格使用daList,插入數據 if (null != list && 0 < list.size()){ insertTable(table, null,list,2); List<Integer[]> indexList = startEnd(list); for (int c=0;c<indexList.size();c++){ //合並行 mergeCellVertically(table,0,indexList.get(c)[0]+1,indexList.get(c)[1]+1); } } index++; } } } /** * 遍歷表格 * @param rows 表格行對象 * @param textMap 需要替換的信息集合 */ public static void eachTable(CustomXWPFDocument document, List<XWPFTableRow> rows , Map<String, Object> textMap){ for (XWPFTableRow row : rows) { List<XWPFTableCell> cells = row.getTableCells(); for (XWPFTableCell cell : cells) { //判斷單元格是否需要替換 if(checkText(cell.getText())){ List<XWPFParagraph> paragraphs = cell.getParagraphs(); for (XWPFParagraph paragraph : paragraphs) { List<XWPFRun> runs = paragraph.getRuns(); for (XWPFRun run : runs) { Object ob = changeValue(run.toString(), textMap); if (ob instanceof String){ run.setText((String)ob,0); }else if (ob instanceof Map){ run.setText("",0); Map pic = (Map)ob; int width = Integer.parseInt(pic.get("width").toString()); int height = Integer.parseInt(pic.get("height").toString()); int picType = getPictureType(pic.get("type").toString()); byte[] byteArray = (byte[]) pic.get("content"); ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteArray); try { int ind = document.addPicture(byteInputStream,picType); document.createPicture(ind, width , height,paragraph); } catch (Exception e) { e.printStackTrace(); } } } } } } } } /** * 為表格插入數據,行數不夠添加新行 * @param table 需要插入數據的表格 * @param tableList 第四個表格的插入數據 * @param daList 第二個表格的插入數據 * @param type 表格類型:1-第一個表格 2-第二個表格 3-第三個表格 4-第四個表格 */ public static void insertTable(XWPFTable table, List<String> tableList,List<String[]> daList,Integer type){ if (2 == type){ //創建行和創建需要的列 for(int i = 1; i < daList.size(); i++){ //添加一個新行 XWPFTableRow row = table.insertNewTableRow(1); for(int k=0; k<daList.get(0).length;k++){ row.createCell();//根據String數組第一條數據的長度動態創建列 } } //創建行,根據需要插入的數據添加新行,不處理表頭 for(int i = 0; i < daList.size(); i++){ List<XWPFTableCell> cells = table.getRow(i+1).getTableCells(); for(int j = 0; j < cells.size(); j++){ XWPFTableCell cell02 = cells.get(j); cell02.setText(daList.get(i)[j]); } } }else if (4 == type){ //插入表頭下面第一行的數據 for(int i = 0; i < tableList.size(); i++){ XWPFTableRow row = table.createRow(); List<XWPFTableCell> cells = row.getTableCells(); cells.get(0).setText(tableList.get(i)); } } } /** * 判斷文本中時候包含$ * @param text 文本 * @return 包含返回true,不包含返回false */ public static boolean checkText(String text){ boolean check = false; if(text.indexOf("$")!= -1){ check = true; } return check; } /** * 匹配傳入信息集合與模板 * @param value 模板需要替換的區域 * @param textMap 傳入信息集合 * @return 模板需要替換區域信息集合對應值 */ public static Object changeValue(String value, Map<String, Object> textMap){ Set<Map.Entry<String, Object>> textSets = textMap.entrySet(); Object valu = ""; for (Map.Entry<String, Object> textSet : textSets) { //匹配模板與替換值 格式${key} String key = textSet.getKey(); if(value.indexOf(key)!= -1){ valu = textSet.getValue(); } } return valu; } /** * 將輸入流中的數據寫入字節數組 * @param in * @return */ public static byte[] inputStream2ByteArray(InputStream in, boolean isClose){ byte[] byteArray = null; try { int total = in.available(); byteArray = new byte[total]; in.read(byteArray); } catch (IOException e) { e.printStackTrace(); }finally{ if(isClose){ try { in.close(); } catch (Exception e2) { System.out.println("關閉流失敗"); } } } return byteArray; } /** * 根據圖片類型,取得對應的圖片類型代碼 * @param picType * @return int */ private static int getPictureType(String picType){ int res = CustomXWPFDocument.PICTURE_TYPE_PICT; if(picType != null){ if(picType.equalsIgnoreCase("png")){ res = CustomXWPFDocument.PICTURE_TYPE_PNG; }else if(picType.equalsIgnoreCase("dib")){ res = CustomXWPFDocument.PICTURE_TYPE_DIB; }else if(picType.equalsIgnoreCase("emf")){ res = CustomXWPFDocument.PICTURE_TYPE_EMF; }else if(picType.equalsIgnoreCase("jpg") || picType.equalsIgnoreCase("jpeg")){ res = CustomXWPFDocument.PICTURE_TYPE_JPEG; }else if(picType.equalsIgnoreCase("wmf")){ res = CustomXWPFDocument.PICTURE_TYPE_WMF; } } return res; } /** * 合並行 * @param table * @param col 需要合並的列 * @param fromRow 開始行 * @param toRow 結束行 */ public static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) { for(int rowIndex = fromRow; rowIndex <= toRow; rowIndex++){ CTVMerge vmerge = CTVMerge.Factory.newInstance(); if(rowIndex == fromRow){ vmerge.setVal(STMerge.RESTART); } else { vmerge.setVal(STMerge.CONTINUE); } XWPFTableCell cell = table.getRow(rowIndex).getCell(col); CTTcPr tcPr = cell.getCTTc().getTcPr(); if (tcPr != null) { tcPr.setVMerge(vmerge); } else { tcPr = CTTcPr.Factory.newInstance(); tcPr.setVMerge(vmerge); cell.getCTTc().setTcPr(tcPr); } } } /** * 獲取需要合並單元格的下標 * @return */ public static List<Integer[]> startEnd(List<String[]> daList){ List<Integer[]> indexList = new ArrayList<Integer[]>(); List<String> list = new ArrayList<String>(); for (int i=0;i<daList.size();i++){ list.add(daList.get(i)[0]); } Map<Object, Integer> tm = new HashMap<Object, Integer>(); for (int i=0;i<daList.size();i++){ if (!tm.containsKey(daList.get(i)[0])) { tm.put(daList.get(i)[0], 1); } else { int count = tm.get(daList.get(i)[0]) + 1; tm.put(daList.get(i)[0], count); } } for (Map.Entry<Object, Integer> entry : tm.entrySet()) { String key = entry.getKey().toString(); String value = entry.getValue().toString(); if (list.indexOf(key) != (-1)){ Integer[] index = new Integer[2]; index[0] = list.indexOf(key); index[1] = list.lastIndexOf(key); indexList.add(index); } } return indexList; } }
CustomXWPFDocument類:
package p1; import java.io.IOException; import java.io.InputStream; 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; /** * Created by 王景偉 on 2018/12/19. * 自定義 XWPFDocument,並重寫 createPicture()方法 */ 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 = getAllPictures().get(id).getPackageRelationship().getId(); CTInline inline = paragraph.createRun().getCTR().addNewDrawing().addNewInline(); System.out.println(blipId+":"+inline); 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("圖片" + id); docPr.setDescr("測試"); }
///測試插入圖片功能
public static void main(String[] args) throws InvalidFormatException, FileNotFoundException, IOException {
//創建一個word文檔對象
CustomXWPFDocument document = new CustomXWPFDocument();
//創建一個段落對象
XWPFParagraph paragraph = document.createParagraph();
//讀取本地的一個圖片文件生成pid
int pid = document.addPicture(new FileInputStream("D:/p1.png"), XWPFDocument.PICTURE_TYPE_PNG);
//創建一個word圖片,並插入到文檔中
document.createPicture(pid, 400, 300,paragraph);
//將文檔對象寫成本地文件
FileOutputStream fos = new FileOutputStream(new File("D:/test.docx"));
document.write(fos);
fos.close();
} }
方法調用:
-
導入jar包
-
復制以上CustomXWPFDocument類和WorderToNewWordUtils類到項目合適路徑(根據自己項目情況判斷)
-
主類調用方法說明(既以上Test測試類)
-
對於一般的字段替換,只需要保持(key,value)的鍵值對方式賦值替換即可,key和模板占位字符保持一致,例:
Map<String, Object> data = new HashMap<String, Object>();
data.put("date","2018−03−06");data.put("{date}", "2018-03-06");data.put("date","2018−03−06");data.put("{name}", “東方明珠”);
data.put("address","華東院");data.put("{address}", "華東院");data.put("address","華東院");data.put("{communityvalue}", “”);
data.put("safetycode","華東院");data.put("{safetycode}", "華東院");data.put("safetycode","華東院");data.put("{picture2}", “”);
data.put("picture3","");data.put("{picture3}", "");data.put("picture3","");data.put("{buildingvalue2}", “華東院”);
data.put("patrolPhoto1","");data.put("{patrolPhoto1}", "");data.put("patrolPhoto1","");data.put("{patrolPhoto2}", “”);data.put("${buildingvalue3}", “中國”); -
如果需要插入圖片,則需要重新創建一個Map集合存儲圖片數據:例
Map<String,Object> picture1 = new HashMap<String, Object>();
picture1.put(“width”, 100);
picture1.put(“height”, 150);
picture1.put(“type”, “jpg”);
picture1.put(“content”, WorderToNewWordUtils.inputStream2ByteArray(new FileInputStream(“D:/timg.jpg”), true));
data.put("${picture1}",picture1);
如果有多張圖片,則需要創建多個Map保存圖片,其中它的屬性值應保持不變(如上:width,height,type,content),需要注意的是對於一般字段或者是圖片最終都存儲在data集合中,如下:
Map<String, Object> data = new HashMap<String, Object>();
- 1
-
對於需要根據具體數據動態生成的表格,我們將數據存儲在一個List集合中,如:
//第一個動態生成的數據列表
List<String[]> list01 = new ArrayList<String[]>();
list01.add(new String[]{“A”,“11111111111”,“22”,“22”});
list01.add(new String[]{“A”,“22222222222”,“33”,“22”});
list01.add(new String[]{“B”,“33333333333”,“44”,“22”});
list01.add(new String[]{“C”,“44444444444”,“55”,“22”});//第二個動態生成的數據列表
List<String[]> list02 = new ArrayList<String[]>();
list02.add(new String[]{“A”,“11111111111”,“22”,“22”});
list02.add(new String[]{“d”,“22222222222”,“33”,“22”});
list02.add(new String[]{“B”,“33333333333”,“44”,“22”});
list02.add(new String[]{“C”,“44444444444”,“55”,“22”});如上是兩個我們需要根據具體數據動態生成的表格,如list01中數據,當list中String數組的第一個字段相同時,則在創建動態表格時會將這兩個單元格合並,其他列數值相同不影響合並。注意:這里的創建的String數組的列數應該和模板的表格列數保持一致。數據保存好之后將list數組統一保存在mapList中,如下:
List mapList = new ArrayList();
注意:
由於無法動態獲取需要動態填充的表格,所以我們定義了一個靜態數組保存需要動態生成的表格的位置,如下://需要動態改變表格的位置;第一個表格的位置為0
int[] placeList = {1,4};
如圖1所示,從上至下表格所在位置分別是0-4;而我們需要動態生成的是第二個和第五個,故此處傳參{1,4}
最后調用方法導出word即可;如下:
CustomXWPFDocument doc = WorderToNewWordUtils.changWord("C:/Users/user/Desktop/test1.docx",data,mapList,placeList);
FileOutputStream fopts = new FileOutputStream("D:/呵呵.docx");
doc.write(fopts);
fopts.close();
在changeWord方法中,第一個參數是模板路徑,第二個參數是填充數據,第三個參數是動態表格填充數據,第四個參數是動態表格位置;
友情提醒:(以下內容若需要可采納,不需要可跳過)
使用poi生成Word會發生分段混亂的問題,例如:在操作POI替換word時發現getRuns將我們預設的${product}自動切換成了成了兩個部分
${product } 或者 product既會出現空格分離字段的情況;建議使用從左往右的順序進行模板字段填充(既:{ product } 既會出現空格分離字段的情況;建議使用從左往右的順序進行模板字段填充(既:product既會出現空格分離字段的情況;建議使用從左往右的順序進行模板字段填充(既:→{→product→} 而不是 $→{→}→product的方式);
親測有效:使用notepad++的xml插件修改模板(保證解析完美)
(一) 安裝notepad++並為其添加插件XML Tools插件(具體步驟自行百度)
(二) 將修改好的word模板另存為xml格式的文檔;如下圖所示
(三) 使用notepad++打開xml模板,剛開始打開是一個毫無邏輯可尋的文件,這時我們使用xml tools工具格式化xml文件:如下:
(四) 如上圖可看出模板被解析成了多部分,此時我們修改文件,讓其保持為一個字段:如下
(五) 保存文檔之后用word打開xml文檔,然后另存為所需要的docx格式word即可
create by 王景偉
2018-12-19