使用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的接口熟悉的话,应该不是很大的难度。这里只是举出一些简单的例子,解决了项目上会常遇到的问题。当然,在实际的项目中,需要根据具体的情况来使用。

展开阅读全文

没有更多推荐了,返回首页