项目开发过程中,客户提出一堆导出报表的需求,需要导出word格式,页眉还需要加上客户公司的logo,试了几种方案,最后选择了用 Apache poi 加上自定义标签的方式实现。
目前功能还比较简单,一些复杂的表格做不了,但是已经基本满足项目需求了。
使用poi读取word模板,替换word中的{text}标签,并根据自定义标签循环生成表格或表格中的行。
代码示例下载:https://download.csdn.net/download/u012775558/10306308
注意,仅支持docx格式的word文件,大概是word2010及以后版本,doc格式不支持。
word模板需要有固定的格式,下面是几个示例与代码实现.
基础文本替换示例
只是最基础的文本替换
模板如下:
生成报表如下:
表格内部循环生成行
注意:
- 表格第一行通过合并单元格的方式,设置为只有两个单元格,第一个单元格填写foreachTableRow标签,第二个单元格填写要替换的数据List名称,本例中是table1或table2,注意名称要和你后台wordDataMap中存入的key值相同。
- 表格第二行是表头。
- 表格第三行需要通过合并单元格的方式,设置为只有一个单元格,填写上foreachRows标签,代表从这一行以下开始循环替换。
- 表格第四行是要替换的数据,在map中的key值。
模板如下:
生成报表如下:
可以给表格加上表头和表尾数据,只需要把数据放入parametersMap(存储报表中不循环的数据)中即可。
表格内部行添加序号示例
你也可以给行加上序号,但是不能直接输入序号,而是通过word的插入编号的功能插入编号,生成的表格才会有编号。
模板如下:
生成报表如下:
也可以给表格加上表头和表尾数据,只需要把数据放入parametersMap(存储报表中不循环的数据)中即可。
循环生成表格示例
注意:
- 表格第一行通过合并单元格的方式,设置为只有两个单元格,第一个单元格填写foreachTable标签,第二个单元格填写要替换的数据List名称,本例中是table1。
- 表格其他部分只需要将要替换的数据用标签替换即可。
模板如下:
生成报表如下
使用方法如下
使用maven搭建项目,引入poi相关jar包。
org.apache.poi poi 3.13 org.apache.poi poi-ooxml 3.13
代码如下
/** * @Title: WordTemplate2.java * @Package: com.highdata.templateTools * @Description: TODO * @author: Juveniless * @date: 2017年11月27日 下午3:23:13 */package com.hidata.tool;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;import org.apache.poi.xwpf.usermodel.BodyElementType;import org.apache.poi.xwpf.usermodel.IBodyElement;import org.apache.poi.xwpf.usermodel.PositionInParagraph;import org.apache.poi.xwpf.usermodel.XWPFDocument;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;/** * * 对docx文件中的文本及表格中的内容进行替换 --模板仅支持对 {key} 标签的替换 * * @ClassName: WordTemplate * @Description: TODO(!!!使用word2013 docx文件) * @author Juveniless * @date: 2017年11月27日 下午3:25:56 * (1)word模板注意页边距的问题,存在问题:比如页边距默认为3cm,画表格时,仍然可以通过 * 拖拽,把表格边框拖动到看起来就像页边距只有1cm的样子,但是实际上此时页边距还是3cm,生成的 * word报表的页边距还是会按照3cm来生成。解决办法,在word文件里,设置好页边距,如果需要表格 * 两边页边距很窄,需要在word里设置页边距窄一点,而不是直接拖动表格边框来实现。 * */public class WordTemplate { private XWPFDocument document; public XWPFDocument getDocument() { return document; } public void setDocument(XWPFDocument document) { this.document = document; } /** * 初始化模板内容 * * @author Juveniless * @date 2017年11月27日 下午3:59:22 * @param inputStream * 模板的读取流(docx文件) * @throws IOException * */ public WordTemplate(InputStream inputStream) throws IOException { document = new XWPFDocument(inputStream); } /** * 将处理后的内容写入到输出流中 * * @param outputStream * @throws IOException */ public void write(OutputStream outputStream) throws IOException { document.write(outputStream); } /** * 根据dataMap对word文件中的标签进行替换; * !!!!***需要注意dataMap的数据格式***!!!! * 对于需要替换的普通标签数据标签(不需要循环)-----必须在dataMap中存储一个key为parametersMap的map, * 来存储这些不需要循环生成的数据,比如:表头信息,日期,制表人等。 * 对于需要循环生成的表格数据------key自定义,value为 --ArrayList<Map<String, String>> * @author Juveniless * @date 2017年11月27日 下午3:29:27 * @param dataMap * */ public void replaceDocument(Map dataMap) { if (!dataMap.containsKey("parametersMap")) { System.out.println("数据源错误--数据源(parametersMap)缺失"); return; } @SuppressWarnings("unchecked") Map parametersMap = (Map ) dataMap .get("parametersMap"); List bodyElements = document.getBodyElements();// 所有对象(段落+表格) int templateBodySize = bodyElements.size();// 标记模板文件(段落+表格)总个数 int curT = 0;// 当前操作表格对象的索引 int curP = 0;// 当前操作段落对象的索引 for (int a = 0; a < templateBodySize; a++) { IBodyElement body = bodyElements.get(a); if (BodyElementType.TABLE.equals(body.getElementType())) {// 处理表格 XWPFTable table = body.getBody().getTableArray(curT); List tables = body.getBody().getTables(); table = tables.get(curT); if (table != null) { // 处理表格 List tableCells = table.getRows().get(0).getTableCells();// 获取到模板表格第一行,用来判断表格类型 String tableText = table.getText();// 表格中的所有文本 if (tableText.indexOf("##{foreach") > -1) { // 查找到##{foreach标签,该表格需要处理循环 if (tableCells.size() != 2 || tableCells.get(0).getText().indexOf("##{foreach") < 0 || tableCells.get(0).getText().trim().length() == 0) { System.out .println("文档中第" + (curT + 1) + "个表格模板错误,模板表格第一行需要设置2个单元格," + "第一个单元格存储表格类型(##{foreachTable}## 或者 ##{foreachTableRow}##),第二个单元格定义数据源。"); return; } String tableType = tableCells.get(0).getText(); String dataSource = tableCells.get(1).getText(); System.out.println("读取到数据源:"+dataSource); if (!dataMap.containsKey(dataSource)) { System.out.println("文档中第" + (curT + 1) + "个表格模板数据源缺失"); return; } @SuppressWarnings("unchecked") List
package com.hidata.tool;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class Test { public static void main(String[] args) throws IOException { Map wordDataMap = new HashMap ();// 存储报表全部数据 Map parametersMap = new HashMap ();// 存储报表中不循环的数据 List > table1 = new ArrayList >(); Map map1=new HashMap<>(); map1.put("name", "张三"); map1.put("age", "23"); map1.put("email", "12121@qq.com"); Map map2=new HashMap<>(); map2.put("name", "李四"); map2.put("age", "45"); map2.put("email", "45445@qq.com"); Map map3=new HashMap<>(); map3.put("name", "Tom"); map3.put("age", "34"); map3.put("email", "6767@qq.com"); table1.add(map1); table1.add(map2); table1.add(map3); List > table2 = new ArrayList >(); Map map4=new HashMap<>(); map4.put("name", "tom"); map4.put("number", "sd1234"); map4.put("address", "上海"); Map map5=new HashMap<>(); map5.put("name", "seven"); map5.put("number", "sd15678"); map5.put("address", "北京"); Map map6=new HashMap<>(); map6.put("name", "lisa"); map6.put("number", "sd9078"); map6.put("address", "广州"); table2.add(map4); table2.add(map5); table2.add(map6); parametersMap.put("userName", "JUVENILESS"); parametersMap.put("time", "2018-03-24"); parametersMap.put("sum", "3"); wordDataMap.put("table1", table1); wordDataMap.put("table2", table2); wordDataMap.put("parametersMap", parametersMap); File file = new File("D:\\Workspaces\\Eclipse 2017\\wordTemplate\\doc\\模板.docx");//改成你本地文件所在目录 // 读取word模板 FileInputStream fileInputStream = new FileInputStream(file); WordTemplate template = new WordTemplate(fileInputStream); // 替换数据 template.replaceDocument(wordDataMap); //生成文件 File outputFile=new File("D:\\Workspaces\\Eclipse 2017\\wordTemplate\\doc\\输出.docx");//改成你本地文件所在目录 FileOutputStream fos = new FileOutputStream(outputFile); template.getDocument().write(fos); }}