使用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//处理中间的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文档(最全例子)相关推荐
- POI生成word文档,包括标题,段落,表格,统计图(非图片格式)
Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能.POI为"P ...
- poi生成word文档,插入图片,echar报表生成到word,word表格
poi生成word文档,word表格,将echar报表生成到word 项目中用到生成word报表,报表中有表格的合并 .页眉.表格中会有报表图片.然后查找了网上的资料,利用echar生成柱状图,然后已 ...
- POI生成word文档完整案例及讲解
一,网上的API讲解 其实POI的生成Word文档的规则就是先把获取到的数据转成xml格式的数据,然后通过xpath解析表单式的应用取值,判断等等,然后在把取到的值放到word文档中,最后在输出来. ...
- POI生成word文档,图片显示为空白或不显示
我想要用java,通过poi实现word文档中插入文字和图片来发送邮箱附件.但是发现在对word操作中,图片是白的,size如果设置小了直接没有图片. 经过百度 参考解决 Java poi 3.8 ...
- 关于Apache / poi 生成word文档之后不能正常换行的问题
近期公司项目有个把文本转成word文档的功能,开始使用io操作输出文件的方式(后缀名是docx),使用手机自带的文档浏览工具打开是没有问题的,但是在电脑上用微软office就打开有问题了,于是找了三方 ...
- poi生成word文档后打开读取失败
电脑安装的office,不论是新创建还是修改之前word模板文件,使用代码poi对其文档修改生成新word就打不开,并提示以下错误: 后台代码也没报错,就是打不开word,网上百度的方法都试了,没解决 ...
- Java POI 生成Word文档
在开发中有时候我们需要导出 word文档.最近因为需要做一个生成word文件的功能.就将这块拿出来和大家分享. 生成word文件和我们写word文档是相同的概念,只不过在这里我们换成了用代码来操作.下 ...
- POI生成word文档,再通过spire.doc.free 实现word转pdf
一.POI实现导出当前页面为word文档 1.导入poi依赖 <dependency><groupId>org.apache.poi</groupId><ar ...
- poi java 导出word_java poi 生成word文档并下载
我使用的是Springboot框架开发的.首先需要在pom.xml文件中引入以下maven包: org.apache.poi poi 3.10-FINAL org.apache.poi poi-oox ...
- Java poi 生成word文档并下载
我使用的是Springboot框架开发的.首先需要在pom.xml文件中引入以下maven包: <dependency><groupId>org.apache.poi</ ...
最新文章
- phpStorm无法使用svn1.8的解决办法
- 动态库的编写和调用 - Delphi
- PHP解决网站大流量与高并发
- EditText 被遮挡和显示不全问题
- 前端学习(1687):前端系列javascript基础面试前言
- SQL Server大数据表的分区存储
- in use 大学英语4word_考研英语真题干货 | run on
- MFC 教程【2_MFC和Win32 】
- HDU 4888 Redraw Beautiful Drawings(2014 Multi-University Training Contest 3)
- 电子书下载:Pro ASP.NET MVC2 Framework 2nd
- 电子版证件照怎么制作并改大小
- android 无法播放mp3文件夹,Android Assets文件夹中的Mp3音频无法通过签名...
- 2019年国内开源镜像站点汇总(已更新,之前的好多不能使用的)
- 微信小程序设置web-view的业务域名
- poi事件模式读取excel
- oeasy教您玩转vim - 23 - 配置文件
- SQL*Net message to client
- ubuntu下修改mysql密码
- 《我的菜谱》-西红柿炒鸡蛋
- 极验验证的滑动验证码破解