1. 程式人生 > >使用poi生成word文件(最全例子)

使用poi生成word文件(最全例子)

1.說明

平時的專案中,我們可能需要做大量的報告。而這些報告中,有些是固定的格式,有些是需要自定義模板來生成。如果是固定的形式的,那麼相對而言是比較好做的,但要是根據模板來生成報告,比如:word、Excel或PDF。這樣的話,可能需要我們花點時間去解決了。這篇部落格主要是帶領大家學習一下,如何用poi技術來實現生成word報告。

2.設計

我們知道,poi的技術可以做出word、Excel、PDF等檔案。在網上也有大量的部落格是關於如何使用poi的ftl的模板來生成word、Excel、PDF。主要是思路是將某個模板(自己定義的,可以是word或Excel)上傳至伺服器,然後解析成對應的ftl檔案,之後服務在做報告時,就可以直接拿取對應的ftl檔案來生成報告。這種方式會比較靈活,現在用的也比較多。但是對於一份簡單的,不需要ftl的檔案來作為中間生成檔案時,那麼可能會比較難了。比如,接下來我們要學習的,使用word的模板作為報告模板,然後直接生成需要的報告。但是word中的所的之間的設定,如字型大小、顏色等都不能改變。這個思路大概是:使用者直接設計一個word模板,然後上傳至伺服器,伺服器將其直接儲存到對應的資料夾下;在要生成報告時,直接拿word的模板來填充資料。這樣,好像也不是很難,但是想想細節上的地方,可能很多東西就不好做了。接下來,我們來說說問題所在。

3.問題

我們在使用word作為一份模板時,首先要解決的時,如何向模板中定義的欄位替換資料,如:{name}要換成真實的改名;其次我們如果要做表格的話,如何向word上直接畫出表格;再次,表格的行列要怎麼合併,多張表應該怎麼複製等;最後,我們應該怎麼將一比我圖片插入到word中。帶著這些問題,我們來一一解決。

4.開發

4.1普通文字替換

在word的檔案中,我們同一個使用“{**}”來標誌需要替換的檔案,如{name}就是要將name的欄位替換成真實的姓名。所以我們需要用正則表示式來尋找到廣本的內容是否包含了“{}”。正則表示式是:\\{.+?\\}。同時,我們需要用poi提供的XWPFRun的介面來替換文字。為了使用替換後的文字可以的換行的操作。比如,我們自己定義的一行文字需要換行,筆者用“@”符號作為標籤,也就是如果遇到內容含有“@”,就要實現換行。而換行是呼叫XWPFRun的addBreak()的方法。部分程式碼如下:

public void replaceParagraph(XWPFParagraph xWPFParagraph, Map<String, Object> parametersMap) {
		List<XWPFRun> runs = xWPFParagraph.getRuns();
		String xWPFParagraphText = xWPFParagraph.getText();
		String regEx = "\\{.+?\\}";
	    Pattern pattern = Pattern.compile(regEx);
	    Matcher matcher = pattern.matcher(xWPFParagraphText);//正則匹配字串{****}

		if (matcher.find()) {
			// 查詢到有標籤才執行替換
			int beginRunIndex = xWPFParagraph.searchText("{", new PositionInParagraph()).getBeginRun();// 標籤開始run位置
			int endRunIndex = xWPFParagraph.searchText("}", new PositionInParagraph()).getEndRun();// 結束標籤
			StringBuffer key = new StringBuffer();

			if (beginRunIndex == endRunIndex) {
				// {**}在一個run標籤內
				XWPFRun beginRun = runs.get(beginRunIndex);
				String beginRunText = beginRun.text();

				int beginIndex = beginRunText.indexOf("{");
				int endIndex = beginRunText.indexOf("}");
				int length = beginRunText.length();

				if (beginIndex == 0 && endIndex == length - 1) {
					// 該run標籤只有{**}
					XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
					insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
					// 設定文字
					key.append(beginRunText.substring(1, endIndex));
					insertNewRun.setText(getValueBykey(key.toString(),parametersMap));
					xWPFParagraph.removeRun(beginRunIndex + 1);
				} else {
					// 該run標籤為**{**}** 或者 **{**} 或者{**}**,替換key後,還需要加上原始key前後的文字
					XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
					insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
					// 設定文字
					key.append(beginRunText.substring(beginRunText.indexOf("{")+1, beginRunText.indexOf("}")));
					String textString=beginRunText.substring(0, beginIndex) + getValueBykey(key.toString(),parametersMap)
							+ beginRunText.substring(endIndex + 1);
					
					insertNewRun.setText(textString);
					xWPFParagraph.removeRun(beginRunIndex + 1);
				}

			}else {
				// {**}被分成多個run

				//先處理起始run標籤,取得第一個{key}值
				XWPFRun beginRun = runs.get(beginRunIndex);
				String beginRunText = beginRun.text();
				int beginIndex = beginRunText.indexOf("{");
				if (beginRunText.length()>1  ) {
					key.append(beginRunText.substring(beginIndex+1));
				}
				ArrayList<Integer> removeRunList = new ArrayList<Integer>();//需要移除的run
				//處理中間的run
				for (int i = beginRunIndex + 1; i < endRunIndex; i++) {
					XWPFRun run = runs.get(i);
					String runText = run.text();
					key.append(runText);
					removeRunList.add(i);
				}

				// 獲取endRun中的key值
				XWPFRun endRun = runs.get(endRunIndex);
				String endRunText = endRun.text();
				int endIndex = endRunText.indexOf("}");
				//run中**}或者**}**
				if (endRunText.length()>1 && endIndex!=0) {
					key.append(endRunText.substring(0,endIndex));
				}



				//*******************************************************************
				//取得key值後替換標籤

				//先處理開始標籤
				if (beginRunText.length()==2 ) {
					// run標籤內文字{
					XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
					insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
					// 設定文字
					insertNewRun.setText(getValueBykey(key.toString(),parametersMap));
					xWPFParagraph.removeRun(beginRunIndex + 1);//移除原始的run
				}else {
					// 該run標籤為**{**或者 {** ,替換key後,還需要加上原始key前的文字
					XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
					insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
					// 設定文字
					String textString=beginRunText.substring(0,beginRunText.indexOf("{"))+getValueBykey(key.toString(),parametersMap);
				//	System.out.println(">>>>>"+textString);
					//分行處理
					if(textString.contains("@"))
					{
						String[] textStrings = textString.split("@");
						for(int i = 0; i < textStrings.length;i++)
						{
							//System.out.println(">>>>>textStrings>>"+textStrings[i]);
							insertNewRun.setText(textStrings[i]);
							//insertNewRun.addCarriageReturn();
							insertNewRun.addBreak();//換行
						}
					}
					else if(textString.endsWith(".png"))
					{
						 CTInline inline = insertNewRun.getCTR().addNewDrawing().addNewInline(); 
	                	 try {
							insertPicture(document,textString,  
							         inline, 500,  
							         200);
							document.createParagraph();// 添加回車換行
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					else
					{
						insertNewRun.setText(textString);
					}
					
					
					xWPFParagraph.removeRun(beginRunIndex + 1);//移除原始的run
				}

				//處理結束標籤
				if (endRunText.length()==1 ) {
					// run標籤內文字只有}
					XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex);
					insertNewRun.getCTR().setRPr(endRun.getCTR().getRPr());
					// 設定文字
					insertNewRun.setText("");
					xWPFParagraph.removeRun(endRunIndex + 1);//移除原始的run

				}else {
					// 該run標籤為**}**或者 }** 或者**},替換key後,還需要加上原始key後的文字
					XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex);
					insertNewRun.getCTR().setRPr(endRun.getCTR().getRPr());
					// 設定文字
					String textString=endRunText.substring(endRunText.indexOf("}")+1);
					insertNewRun.setText(textString);
					xWPFParagraph.removeRun(endRunIndex + 1);//移除原始的run
				}

				//處理中間的run標籤
				for (int i = 0; i < removeRunList.size(); i++) {
					XWPFRun xWPFRun = runs.get(removeRunList.get(i));//原始run
					XWPFRun insertNewRun = xWPFParagraph.insertNewRun(removeRunList.get(i));
					insertNewRun.getCTR().setRPr(xWPFRun.getCTR().getRPr());
					insertNewRun.setText("");
					xWPFParagraph.removeRun(removeRunList.get(i) + 1);//移除原始的run
				}

			}// 處理${**}被分成多個run

			replaceParagraph( xWPFParagraph, parametersMap);

		}//if 有標籤

	}

4.2實現表格內行迴圈

     為了達到一份表格的行可以迴圈,我們使用”##{foreachTableRow}##”,說明同一表格的行需要迴圈。該標籤定義到表格的最上方。然而,表格中會有固定的欄位名,其內容 是需要迴圈的,所以我們用“##{foreachRows}##”來表示,行需要迴圈。表格設計如下:

表格中的{project}{class}無非就是內容迴圈了。部分程式碼如下:

XWPFTable newCreateTable = document.createTable();// 建立新表格,預設一行一列
			List<XWPFTableRow> TempTableRows = templateTable.getRows();// 獲取模板表格所有行
			int tagRowsIndex = 0;// 標籤行indexs
			for (int i = 0, size = TempTableRows.size(); i < size; i++) {
				String rowText = TempTableRows.get(i).getCell(0).getText();// 獲取到表格行的第一個單元格
				if (rowText.indexOf("##{foreachRows}##") > -1) {
					tagRowsIndex = i;
					break;
				}
			}

			/* 複製模板行和標籤行之前的行  即第一行的內容*/
			for (int i = 1; i < tagRowsIndex; i++) {
				XWPFTableRow newCreateRow = newCreateTable.createRow();
				CopyTableRow(newCreateRow, TempTableRows.get(i));// 複製行
				replaceTableRow(newCreateRow, parametersMap);// 處理不迴圈標籤的替換
			}

			/* 迴圈生成模板行 */
			XWPFTableRow tempRow = TempTableRows.get(tagRowsIndex + 1);// 獲取到模板行
			for (int i = 0; i < list.size(); i++) {
				XWPFTableRow newCreateRow = newCreateTable.createRow();
				CopyTableRow(newCreateRow, tempRow);// 複製模板行
				replaceTableRow(newCreateRow, list.get(i));// 處理標籤替換
			}

			/* 複製模板行和標籤行之後的行 */
			for (int i = tagRowsIndex + 2; i < TempTableRows.size(); i++) {
				XWPFTableRow newCreateRow = newCreateTable.createRow();
				CopyTableRow(newCreateRow, TempTableRows.get(i));// 複製行
				replaceTableRow(newCreateRow, parametersMap);// 處理不迴圈標籤的替換
			}
			
			newCreateTable.removeRow(0);// 移除多出來的第一行
			document.createParagraph();// 添加回車換行

4.3實現整個表格迴圈

有時候,我們需要是寫一整段的內容,並這段內容的某個值是不一樣的,但大部分是一樣的,且要迴圈輸出。這個時候,我們可以借用表格來實現。也就是定義個表格迴圈,而表格的邊框設定為隱藏的,這樣就可以實現我們的需求了。定義的表格樣式如下:

圖片中是一個表格,只是邊框被筆者隱藏了。從上面的圖片可以看出,“##{foreachTable}##”是需要表格迴圈,而其它的“{**}”都只是內容替換。部分程式碼如下:

for (Map<String, Object> map : list) {
				List<XWPFTableRow> templateTableRows = templateTable.getRows();// 獲取模板表格所有行
				XWPFTable newCreateTable = document.createTable();// 建立新表格,預設一行一列
				for (int i = 1; i < templateTableRows.size(); i++) {
					XWPFTableRow newCreateRow = newCreateTable.createRow();
					CopyTableRow(newCreateRow, templateTableRows.get(i));// 複製模板行文字和樣式到新行
					
					/*表格複製時,邊框不顯示*/
					for (int j = 0; j < newCreateRow.getTableCells().size(); j++) {
						 CTTcBorders tblBorders = newCreateRow.getCell(j).getCTTc().getTcPr().addNewTcBorders();
						   tblBorders.addNewLeft().setVal(STBorder.NIL);
						   tblBorders.addNewRight().setVal(STBorder.NIL);
						   tblBorders.addNewBottom().setVal(STBorder.NIL);
						   tblBorders.addNewTop().setVal(STBorder.NIL);
						   newCreateRow.getCell(j).getCTTc().getTcPr().setTcBorders(tblBorders);
					}
				}
				newCreateTable.removeRow(0);// 移除多出來的第一行
				document.createParagraph();// 添加回車換行
				replaceTable(newCreateTable, map);//替換標籤
			}

4.4實現表格和行一起迴圈

上面我們已經介紹了同一份表格中的行迴圈,及一份單獨表格迴圈。那麼如果遇到即要行迴圈又要表格迴圈,那麼應該怎麼實現呢?拉下來我們就要實現這樣的功能。現在先來看一下我們設計的模板:

該模板中的”##{foreachTableRowTable}##”就是說明表格的行都要迴圈,“##{foreachRows}##”是行迴圈。我們來看一下部分程式碼實現:

@SuppressWarnings("unchecked")
							Map<String,List<Map<String, Object>>> tableDataList = (Map<String,List<Map<String, Object>>>) dataMap
									.get(dataSource);
							table.getRow(1).getCell(0).setText("aaa1111");
							//XWPFRun endRun = runs.get(0);
							Map<String,Object> dd = new HashMap<String,Object>();
							dd.put("aa","ssssssssss");
						
							List<XWPFTableCell> cells = table.getRow(1).getTableCells();
							for (XWPFTableCell xWPFTableCell : cells) {
								List<XWPFParagraph> paragraphs = xWPFTableCell.getParagraphs();
								for (XWPFParagraph xwpfParagraph : paragraphs) {

									replaceParagraph(xwpfParagraph, dd);
								}
							}
							addTableInDocFooter(table, tableDataList, parametersMap, "aaa");
							addTableInDocFooter(table, tableDataList, parametersMap, "bbb");

4.5跨行合併

以下程式碼就是實現跨行合併。

 public  void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {  
        for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {  
            XWPFTableCell cell = table.getRow(rowIndex).getCell(col);  
            if ( rowIndex == fromRow ) {  
                // The first merged cell is set with RESTART merge value  
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);  
            } else {  
                // Cells which join (merge) the first one, are set with CONTINUE  
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);  
            }  
        }  
    }

4.6跨列合併

以下程式碼就是實現跨列合併。

public  void mergeCellsHorizontal(XWPFTable table, int row, int fromCell, int toCell) {  
        for (int cellIndex = fromCell; cellIndex <= toCell; cellIndex++) {  
            XWPFTableCell cell = table.getRow(row).getCell(cellIndex);  
            if ( cellIndex == fromCell ) {  
                // The first merged cell is set with RESTART merge value  
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);  
            } else {  
                // Cells which join (merge) the first one, are set with CONTINUE  
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);  
            }  
        }  
    }  

4.7插入圖片

前面我們已經說了,對於一份word文件,我們應該怎樣實現插入一張圖片。筆者在這裡用的方法是,傳送一個路徑,這個路徑就是圖片所放置的路徑,然後插入圖片就可以了。

但是word應該怎麼插入了。筆者使用的程式碼如下:
 

/** 
     * insert Picture 
     * @param document 
     * @param filePath 
     * @param inline 
     * @param width 
     * @param height 
     * @throws InvalidFormatException 
     * @throws FileNotFoundException 
     */  
    private static void insertPicture(XWPFDocument document, String filePath,  
                               CTInline inline, int width,  
                               int height) throws InvalidFormatException,  
                                                  FileNotFoundException {  
        document.addPictureData(new FileInputStream(filePath),XWPFDocument.PICTURE_TYPE_PNG);  
        int id = document.getAllPictures().size() - 1;  
        final int EMU = 9525;  
        width *= EMU;  
        height *= EMU;  
        String blipId =  
            document.getAllPictures().get(id).getPackageRelationship().getId();  
        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);  
    }  
    
    /** 
     * get the xml of the picture 
     * @param blipId 
     * @param width 
     * @param height 
     * @return 
     */  
    private static String getPicXml(String blipId, int width, int height) {  
        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=\"" + 0 +  
            "\" 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>";  
        return picXml;  
    }

上面的程式碼,會先呼叫方法insertPicture,然後再呼叫方法getPicXml,就可以實現插入圖片。

以上就是筆者帶著大夥兒解決使用word模板時遇到的問題,希望對大夥兒有幫助。

5.專案JAR檔案

  使用poi技術,我們需要以下的jar包

6.總結

這篇部落格就是帶大家使用poi的技術,來實現生成一份word的文件。只要對poi的介面熟悉的話,應該不是很大的難度。這裡只是舉出一些簡單的例子,解決了專案上會常遇到的問題。當然,在實際的專案中,需要根據具體的情況來使用。