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//处理中间的runfor (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 blocke.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));//原始runXWPFRun insertNewRun = xWPFParagraph.insertNewRun(removeRunList.get(i));insertNewRun.getCTR().setRPr(xWPFRun.getCTR().getRPr());insertNewRun.setText("");xWPFParagraph.removeRun(removeRunList.get(i) + 1);//移除原始的run}}// 处理${**}被分成多个runreplaceParagraph( xWPFParagraph, parametersMap);}//if 有标签}

4.2实现表格内行循环

为了达到一份表格的行可以循环,我们使用”##{foreachTableRow}##”,说明同一表格的行需要循环。该标签定义到表格的最上方。然而,表格中会有固定的字段名,其内容 是需要循环的,所以我们用“##{foreachRows}##”来表示,行需要循环。表格设计如下:

表格中的{project}{class}无非就是内容循环了。部分代码如下:

XWPFTable newCreateTable = document.createTable();// 创建新表格,默认一行一列List<XWPFTableRow> TempTableRows = templateTable.getRows();// 获取模板表格所有行int tagRowsIndex = 0;// 标签行indexsfor (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的接口熟悉的话,应该不是很大的难度。这里只是举出一些简单的例子,解决了项目上会常遇到的问题。当然,在实际的项目中,需要根据具体的情况来使用。

使用poi生成word文档(最全例子)相关推荐

  1. POI生成word文档,包括标题,段落,表格,统计图(非图片格式)

    Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能.POI为"P ...

  2. poi生成word文档,插入图片,echar报表生成到word,word表格

    poi生成word文档,word表格,将echar报表生成到word 项目中用到生成word报表,报表中有表格的合并 .页眉.表格中会有报表图片.然后查找了网上的资料,利用echar生成柱状图,然后已 ...

  3. POI生成word文档完整案例及讲解

    一,网上的API讲解 其实POI的生成Word文档的规则就是先把获取到的数据转成xml格式的数据,然后通过xpath解析表单式的应用取值,判断等等,然后在把取到的值放到word文档中,最后在输出来. ...

  4. POI生成word文档,图片显示为空白或不显示

    我想要用java,通过poi实现word文档中插入文字和图片来发送邮箱附件.但是发现在对word操作中,图片是白的,size如果设置小了直接没有图片.  经过百度 参考解决 Java poi 3.8 ...

  5. 关于Apache / poi 生成word文档之后不能正常换行的问题

    近期公司项目有个把文本转成word文档的功能,开始使用io操作输出文件的方式(后缀名是docx),使用手机自带的文档浏览工具打开是没有问题的,但是在电脑上用微软office就打开有问题了,于是找了三方 ...

  6. poi生成word文档后打开读取失败

    电脑安装的office,不论是新创建还是修改之前word模板文件,使用代码poi对其文档修改生成新word就打不开,并提示以下错误: 后台代码也没报错,就是打不开word,网上百度的方法都试了,没解决 ...

  7. Java POI 生成Word文档

    在开发中有时候我们需要导出 word文档.最近因为需要做一个生成word文件的功能.就将这块拿出来和大家分享. 生成word文件和我们写word文档是相同的概念,只不过在这里我们换成了用代码来操作.下 ...

  8. POI生成word文档,再通过spire.doc.free 实现word转pdf

    一.POI实现导出当前页面为word文档 1.导入poi依赖 <dependency><groupId>org.apache.poi</groupId><ar ...

  9. poi java 导出word_java poi 生成word文档并下载

    我使用的是Springboot框架开发的.首先需要在pom.xml文件中引入以下maven包: org.apache.poi poi 3.10-FINAL org.apache.poi poi-oox ...

  10. Java poi 生成word文档并下载

    我使用的是Springboot框架开发的.首先需要在pom.xml文件中引入以下maven包: <dependency><groupId>org.apache.poi</ ...

最新文章

  1. phpStorm无法使用svn1.8的解决办法
  2. 动态库的编写和调用 - Delphi
  3. PHP解决网站大流量与高并发
  4. EditText 被遮挡和显示不全问题
  5. 前端学习(1687):前端系列javascript基础面试前言
  6. SQL Server大数据表的分区存储
  7. in use 大学英语4word_考研英语真题干货 | run on
  8. MFC 教程【2_MFC和Win32 】
  9. HDU 4888 Redraw Beautiful Drawings(2014 Multi-University Training Contest 3)
  10. 电子书下载:Pro ASP.NET MVC2 Framework 2nd
  11. 电子版证件照怎么制作并改大小
  12. android 无法播放mp3文件夹,Android Assets文件夹中的Mp3音频无法通过签名...
  13. 2019年国内开源镜像站点汇总(已更新,之前的好多不能使用的)
  14. 微信小程序设置web-view的业务域名
  15. poi事件模式读取excel
  16. oeasy教您玩转vim - 23 - 配置文件
  17. SQL*Net message to client
  18. ubuntu下修改mysql密码
  19. 《我的菜谱》-西红柿炒鸡蛋
  20. 极验验证的滑动验证码破解

热门文章

  1. 前台跨站点获取session
  2. 转:使用Android API最佳实践
  3. Visual Studio.NET 无法创建或打开应用程序之解决方法
  4. MPC模型预测控制器——数学推导
  5. MySql中的count函数
  6. HCIE-RS面试--STP弊端
  7. The Top 8 Security and Risk Trends We’re Watching
  8. HDOJ--1495--非常可乐(隐式图)
  9. Android P Beta!您想要知道的所有更新内容都在这里
  10. 047——VUE中css过渡动作实例