Java生成 word报告 在网上找了好多天将数据库信息导出到 word 中的解决方案,现在将这几天的总结分享一下。总的来说,Java 导出 word 大致有 5 种。

在网上找了好多天将数据库信息导出到 word 中的解决方案,现在将这几天的总结分享一下。总的来说,Java 导出 word 大致有 5 种。

一、方案比较

1. Poi-tl

poi-tl(poi template language)是 Word 模板引擎,使用模板和数据创建很棒的 Word 文档。基于 Apache POI,提供更友好的API。

优点:低代码,准备文档模版和数据即可,大大简化了 word 和 excel 文档的创建和修改。

缺点:依赖于 Apache POI,有版本要求;对于复杂文档的操作,可能需要编写较为复杂的代码。

2. Apache POI

Apache POI 包括一系列的 API,它们可以操作基于 MicroSoft OLE 2 Compound Document Format的各种格式文件,可以通过这些API在Java中读写Excel、Word等文件。

优点:跨平台支持 windows、unix 和 linux。

缺点:相对与 word 文件的处理来说,POI 更适合 excel 处理,对于 word 实现一些简单文件的操作凑合,不能设置样式且生成的 word 文件格式不够规范。

3. Java2word

Java2word 是一个在 Java 程序中调用 MS Office Word 文档的组件(类库)。该组件提供了一组简单的接口,以便 Java 程序调用它的服务操作 word 文档。这些服务包括:打开文档、新建文档、查找文字、替换文字、插入文字、插入图片、插入表格,在书签处插入文字、插入图片、插入表格等。

优点:足够简单,操作起来要比 FreeMarker 简单的多。

缺点:没有 FreeMarker 强大,不能根据模板生成 word 文档,word 文档的样式等信息都不能够很好的操作。

4. FreeMarker

FreeMarker 生成 word 文档的功能是由 XML + FreeMarker 来实现的。先把 word 文件另存为 xml,在 xml 文件中插入特殊字符串占位符,将 xml 翻译为 FreeMarker 模板,最后用 Java 来解析 FreeMarker 模板,编码调用 FreeMarker 实现文本替换并输出 doc。

优点:比 Java2word 功能强大,也是纯 Java 编程。

缺点:生成的文件本质上是 xml,不是真正的 word 文件格式,有很多常用的 word 格式无法处理或表现怪异,比如:超链接、换行、乱码、部分生成的文件打不开等。

5. PageOffice

PageOffice 生成 word 文件。PageOffice 封装了微软 Office 繁琐的 vba 接口,提供了简洁易用的 Java 编程对象,支持生成 word 文件,同时实现了在线编辑 word 文档和读取 word 文档内容。

优点:跨平台支持 windows、unix 和 linux,生成 word 文件格式标准,支持文本、图片、表格、字体、段落、颜色、超链接、页眉等各种格式的操作,支持多 word 合并,无需处理并发,不耗费服务器资源,运行稳定。

缺点:必须在客户端生成文件(可以不显示界面),不支持纯服务器端生成文件

二、Poi-tl 生成

  1. 前提

    • JDK 1.8+
    • Apache POI 5.2.2+
  2. 依赖:

    
     com.deepoove
     poi-tl
     1.12.2
    
  3. 模版

    模版是 docx 格式的 Word 文档,你可以使用 Microsoft office、WPS Office、Pages 等任何你喜欢的软件制作模版,也可以使用 Apache POI 代码来生成模版。

    所有的标签都是以{{开头,以}}结尾,标签可以出现在任何位置,包括页眉、页脚、表格内部、文本框等,表格布局可以设计出很多优秀专业的文档,推荐使用表格布局。

    poi-tl模板遵循 “ 所见即所得 ” 的设计,模板和标签的样式会被完全保留。

  4. 数据

    数据类似于哈希或者字典,可以是 Map 结构(key 是标签名称):

    Map data = new HashMap();
    data.put("name", "Sayi");
    data.put("start_time", "2019-08-04");

    可以是对象(属性名是标签名称):

    public class Data {
    	private String name;
    	private String startTime;
    	private Author author;
    }

    数据可以是树结构,每级之间用点来分隔开,比如{{author.name}}标签对应的数据是 author 对象的 name 属性值。

  5. 输出

    以流的方式进行输出:

    template.write(OutputStream stream);

    比如文件流:

    template.write(new FileOutputStream("output.docx"));

    比如网络流:

    response.setContentType("application/octet-stream");
    response.setHeader("Content-disposition","attachment;filename=\""+"out_template.docx"+"\"");
    // HttpServletResponse response
    OutputStream out = response.getOutputStream();
    BufferedOutputStream bos = new BufferedOutputStream(out);
    template.write(bos);
    bos.flush();
    out.flush();

    最后不要忘记关闭这些流。

    PoitlIOUtils.closeQuietlyMulti(template, bos, out);
  6. 实战

    具体设置可以参考文档
    话不多说,我们上模版

    这里把部分可以写死的标题设置成文本,可以在代码里根据是否 put 控制是否展示,如上图中的{{r16}}

    public void rs_export_detail(IdReq req, HttpServletResponse response) {
     HashMap map = getMap(req);
    // XWPFTemplate template = XWPFTemplate.compile(tempPath + "/template/rsT.docx").render(map);
     XWPFTemplate template = XWPFTemplate.compile("E:\\Java Job\\项目路径\\src\\main\\resources\\templates\\rsT.docx").render(map);
     // 指定文件名
     response.setContentType("application/octet-stream");
     response.setCharacterEncoding("utf-8");
     String fileName = "维修工单" + nowDateTime + ".docx";
     response.setHeader("Content-disposition","attachment;filename=\""+ fileName +"\"" );
     try {
     OutputStream out = response.getOutputStream();
     BufferedOutputStream bos = new BufferedOutputStream(out);
     template.write(bos);
     bos.flush();
     out.flush();
     PoitlIOUtils.closeQuietlyMulti(template, bos, out);
     } catch (IOException e) {
     e.printStackTrace();
     }
    }
    HashMap getMap(IdReq req) {
    	//查询数据
     RsDetailResp rs = Optional.ofNullable(rs_detail(req)).orElseThrow(() -> new CustomException("id不存在")).getData();
     RsBindDetailResp rsBind = Optional.ofNullable(rs_bind_detail(req)).orElseThrow(() -> new CustomException("id不存在")).getData();
     //将数据传入map集合中
     HashMap map = new HashMap();
     map.put("serialNumber",rs.getSerialNumber());
     map.put("unitName",rs.getUnitName());
     map.put("projectName",rs.getProjectName());
     map.put("dangerGrade",getDangerGrade(rs.getDangerGrade()));
     map.put("dangerType","消防维保");
     map.put("handleType","转维修");
     map.put("createUserName",rs.getCreateUserName());
     map.put("createUserPhone",rs.getCreateUserPhone());
     map.put("contactsUserName",rs.getContactsUserName());
     map.put("contactsUserPhone",rs.getContactsUserPhone());
     map.put("remark",rs.getRemark());
     map.put("r16", Texts.of("报修照片").bold().create());
     map.put("rsFile", docxImg(rs.getRsFile()));
     map.put("rsUserIdName",rs.getRsUserIdName());
     map.put("rsUserIdPhone",rs.getRsUserIdPhone());
     List deviceList = rs.getDeviceList();
     if (deviceList.size()>0){
     RowRenderData row0_d = Rows.of("序号", "设备", "子设备").textColor("FFFFFF").bgColor("4472C4").center().create();
     TableRenderData tD = Tables.create().addRow(row0_d);
     for (int i = 0; i < deviceList.size(); i++) {
     RsBindInfo bd = deviceList.get(i);
     RowRenderData row = Rows.create(i+"", bd.getTypeName(), bd.getName());
     tD.addRow(row);
     }
     map.put("r1", Texts.of("问题设备").bold());
     map.put("deviceList", tD);
     }
     map.put("diagnosis",rs.getDiagnosis());
     List stopDeviceList = rs.getStopDeviceList();
     StringBuilder str_d = new StringBuilder();
     for (RsBindInfo s:stopDeviceList){
     str_d.append(s.getName()).append(" ");
     }
     map.put("stopDeviceList", rs.getIsStopSystem()==1? str_d.toString() :"-");
     List stopBuildingList = rs.getStopBuildingList();
     StringBuilder str_b = new StringBuilder();
     for (RsBindInfo s:stopBuildingList){
     str_b.append(s.getName()).append(" ");
     }
     map.put("stopBuildingList", str_b);
     map.put("isReplace",rs.getIsReplace()==1?"是":"否");
     map.put("isConfirm",rs.getIsConfirm()==1?"是":"否");
     map.put("measure",rs.getMeasure());
     map.put("methods",rs.getMethods());
     map.put("r15", Texts.of("维修方案耗材及更换设备清单").color("1eaafe").bold().create());
     return map;
    }

    结果展示:

三、Apache POI 生成

基本概念
XWPFDocument代表一个 docx 文档
XWPFParagraph代表文档、表格、标题等各种的段落,由多个XWPFRun组成
XWPFRun代表具有同样风格的一段文本
XWPFTable代表一个表格
XWPFTableRow代表表格的一行
XWPFTableCell代表表格的一个单元格
XWPFChar表示.docx文件中的图表
XWPFHyperlink表示超链接
XWPFPicture代表图片
XWPFComment代表批注
XWPFFooter代表页脚
XWPFHeader代表页眉
XWPFStyles样式(设置多级标题的时候用)
  1. 依赖:

	org.apache.poi
	poi
	4.1.2
	org.apache.poi
	poi-ooxml
	4.1.2
  1. 正文段落

一个文档包含多个段落,一个段落包含多个 Runs,一个 Runs 包含多个 Run,Run 是文档的最小单元

获取所有段落:List paragraphs = word.getParagraphs();

获取一个段落中的所有 Runs:List xwpfRuns = xwpfParagraph.getRuns();

获取一个 Runs 中的一个 Run:XWPFRun run = xwpfRuns.get( index );

XWPFRun 代表具有相同属性的一段文字

  1. 正文表格

一个文档包含多个表格,一个表格包含多行,一行包含多列(格),每一格的内容相当于一个完整的文档

获取所有表格:List xwpfTables = doc.getTable();

获取一个表格的行数:int rcount = xwpfTable.getNumberOfRows();

获取一个表格的第几行:XWPFTableRow row = table.getRow( i );

获取一个表格中的所有行:List xwpfTableRows = xwpfTable.getRows();

获取一行中的所有列:List xwpfTableCells = xwpfTableRow.getTableCells();

获取一格里的内容:List paragraphs = xwpfTableCell.getParagraphs();

  • 表格的一格相当于一个完整的 docx 文档,只是没有页眉和页脚。里面可以有表格,使用 xwpfTableCell.getTable() 获取等等
  • 在 POI 文档中段落和表格是完全分开的,如果在两个段落中有一个表格,在 POI 中是没办法确定表格在段落中间的。只有文档的格式固定,才能正确的得到文档的结构。
  1. 页眉

一个文档可以有多个页眉,页眉里面可以包含段落和表格

获取文档的页眉:List headerList = doc.getHeaderList();

获取页眉里面的所有段落:List paras = header.getParagraphs();

获取页眉里的所有表格:List tables = header.getTables();

  1. 页脚

页脚和页眉基本类似,可以获取表示页数的角标

  1. 参考

(1) POI 创建 word 文档简单示例

XWPFDocument doc = new XWPFDocument();// 创建Word文件
XWPFParagraph p = doc.createParagraph();// 新建一个段落
p.setAlignment(ParagraphAlignment.CENTER);// 设置段落的对齐方式
p.setBorderBottom(Borders.DOUBLE);// 设置下边框
p.setBorderTop(Borders.DOUBLE);// 设置上边框
p.setBorderRight(Borders.DOUBLE);// 设置右边框
p.setBorderLeft(Borders.DOUBLE);// 设置左边框
XWPFRun r = p.createRun();// 创建段落文本
r.setText("POI创建的Word段落文本");
r.setBold(true);// 设置为粗体
r.setColor("FF0000");// 设置颜色
p = doc.createParagraph();// 新建一个段落
r = p.createRun();
r.setText("POI读写Excel功能强大、操作简单。");
XWPFTable table= doc.createTable(3, 3);// 创建一个表格
table.getRow(0).getCell(0).setText("表格1");
table.getRow(1).getCell(1).setText("表格2");
table.getRow(2).getCell(2).setText("表格3");
FileOutputStream out = new FileOutputStream("d:\\POI\\sample.doc");
doc.write(out);
out.close();


(2) POI 读取 word 文档里的文字

FileInputStream stream = new FileInputStream("d:\\POI\\sample.doc");
XWPFDocument doc = new XWPFDocument(stream); //创建Word文件
for(XWPFParagraph p : doc.getParagraphs()){ //遍历段落
	System.out.print(p.getParagraphText());
}
for(XWPFTable table : doc.getTables()){ //遍历表格
	for(XWPFTableRow row : table.getRows()){
	for(XWPFTableCell cell : row.getTableCells()){
	System.out.print(cell.getText());
	}
	}
}

(3) 代码示例

该代码示例只是一个 demo,简单生成了一个 word 文件,包括 word 文档常规的一些样式设置:文件字体、首行缩进、文字大小、段落对齐方式、换行。还有常见的表格创建,以及表格合并效果等。

代码:

public void createFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
 //创建文本对象
 XWPFDocument docxDocument = new XWPFDocument();
 //创建第一段落
 XWPFParagraph firstParagraphX = docxDocument.createParagraph();
 firstParagraphX.setAlignment(ParagraphAlignment.CENTER);
 XWPFRun runTitle = firstParagraphX.createRun();
 runTitle.setText("医院报告申请"); //标题
 runTitle.setBold(true); //加粗
 runTitle.setFontSize(24); //文字大小
 runTitle.setFontFamily("宋体"); //字体
 runTitle.addCarriageReturn(); //回车键
 runTitle.setKerning(30); //字间距
 XWPFParagraph paragraphX = docxDocument.createParagraph();
 paragraphX.setAlignment(ParagraphAlignment.LEFT); //对齐方式
 paragraphX.setFirstLineIndent(400); //首行缩进
 //创建段落中的run
 XWPFRun run = paragraphX.createRun();
 run.setText("开始新的额一页了健康卡离开了危,机容量为金融界王仁君我快速建房可谓集,有分页吗,按时交付问我问问");
 //run.addCarriageReturn();//回车键
 XWPFRun run2 = paragraphX.createRun();
 run2.setText("这是第二段了吧,接口了就废了我今儿来将危及,不知道嗯么回事了了,啦啦啦啦啦啦啦");
 run2.setText("这个不是能分段吗,测试一下试试");
 run2.setBold(true);//加粗
 //创建第二段落
 XWPFParagraph paragraphX2 = docxDocument.createParagraph();
 paragraphX2.setIndentationFirstLine(420); //首行缩进
 XWPFRun secondRun = paragraphX2.createRun();
 secondRun.setText("第二天的开始,就忙吧尽快立法捡垃圾而");
 secondRun.setColor("FFC0CB");
 secondRun.setUnderline(UnderlinePatterns.SINGLE);
 secondRun.addCarriageReturn();
 //创建表格 4行*5列(创建table 时,会有一个默认一行一列的表格)
 XWPFTable table = docxDocument.createTable(4,5);
 table.setWidth("95%");
 table.setWidthType(TableWidthType.PCT); //设置表格相对宽度
 table.setTableAlignment(TableRowAlign.CENTER); //居中对齐
 //合并单元格
 XWPFTableRow row1 = table.getRow(0); //第一行
 XWPFTableCell cell1 = row1.getCell(0); //第一行的第一列
 CTTcPr cellCtPr = getCellCTTcPr(cell1);
 cellCtPr.addNewHMerge().setVal(STMerge.RESTART);
 XWPFTableCell cell2 = row1.getCell(1); //第一行的第二列
 CTTcPr cellCtPr2 = getCellCTTcPr(cell2);
 cellCtPr2.addNewHMerge().setVal(STMerge.CONTINUE);
 //给表格填充文本
 setTableText(docxDocument);
 XWPFParagraph endParagraphX = docxDocument.createParagraph();
 endParagraphX.setAlignment(ParagraphAlignment.RIGHT);
 XWPFRun endRun = endParagraphX.createRun();
 endRun.setText("2021年11月18日");
 String path="D://POI//docBoke.docx";
 File file = new File(path);
 FileOutputStream stream = new FileOutputStream(file);
 docxDocument.write(stream);
 stream.close();
 System.out.println("文件生成完成!");
}
private void setTableText(XWPFDocument docxDocument) {
 //获取第一个表格
 XWPFTable table = docxDocument.getTableArray(0);
 List rows = table.getRows();
 int i=1;
 for(XWPFTableRow row :rows){
 List cells = row.getTableCells();
 for(XWPFTableCell cell: cells){
 cell.setText("第"+String.valueOf(i++)+"格");
 cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); //中心垂直对齐
 //cell.setWidthType(TableWidthType.PCT);
 //cell.setWidth("30%");
 }
 }
}
public static CTTcPr getCellCTTcPr(XWPFTableCell cell) {
 CTTc cttc = cell.getCTTc();
 CTTcPr tcPr = cttc.isSetTcPr() ? cttc.getTcPr() : cttc.addNewTcPr();
 return tcPr;
}

运行之后,就会在指定目录下生成名为 “ docBoke.docx ” 的 word 报告文件

四、FreeMarker 生成

FreeMarker 是一个基于 Java 的模板引擎,最初专注于使用 MVC 软件架构生成动态网页。但是,它是一个通用的模板引擎,不依赖于 servlets 或 HTTP 或 HTML,因此它通常还用于生成源代码,配置文件或电子邮件。
此时,我们用它动态生成的 xml 文件,进而导出 word 文档。

  1. 流程图
  2. 模板制作
    先用 word 做一个模板,如下图:

(注意,上面是有表格的,我设置了边框不可见)然后另存为 xml 文件,之后用工具打开这个 xml 文件,有人用 firstobject xml editor 感觉还不如 notePad++ ,我这里用 notePad++,主要是有高亮显示,和元素自动配对,效果如下:

上面黑色的地方基本是我们之后要替换的地方,比如xytitle替换为${xytitle},对于表格要十分注意,比如选择题下面的表格,我们可以通过<w:tr>查找来定位,一对<w:tr></w:tr>代表一行,也就是一条记录(一道题),我们这里要用一对<#list></#list>来降其包括,以便后续填充数据,具体可以参照FreeMarker页面语法
例如这里选择题,我们是两行为一条记录,所以要<#list></#list>包括两行,形如:
<#list table as plan1><w:tr>题号 题目</w:tr><w:tr>选项</w:tr></#list>
然后在这其中找到对应的xzn,xztest,ans1,ans2,ans3,ans4替换为${plan1.xzn},${plan1.xztest},${plan1.ans1},${plan1.ans2},${plan1.ans3},${plan1.ans4},注意这里的table1plan1命名,table1后续填充数据要用到,其他的替换同理操作,
将 xml 文件格式化:在线格式化


全部参数预设完成后保存,修改后缀名为ftl,至此模板制作完毕。

  1. 代码实现

依赖:


org.freemarker
freemarker
2.3.9

代码:

public class DocumentHandler {
 private Configuration configuration = null;
 
 public DocumentHandler() {
 configuration = new Configuration();
 configuration.setDefaultEncoding("utf-8");
 }
	/*
 * 转换成word
 */
 public void createDoc(Map dataMap, String fileName) throws UnsupportedEncodingException {
 //dataMap 要填入模本的数据文件
 //设置模本装置方法和路径,FreeMarker支持多种模板装载方法。可以重servlet,classpath,数据库装载,
 //这里我们的模板是放在template包下面
 configuration.setClassForTemplateLoading(this.getClass(), "/templates");
 Template t=null;
 try {
 //.ftl文件为要装载的模板
 t = configuration.getTemplate("document.ftl");
 } catch (IOException e) {
 e.printStackTrace();
 }
 //输出文档路径及名称
 File outFile = new File(fileName);
 Writer out = null;
 FileOutputStream fos=null;
 try {
 fos = new FileOutputStream(outFile);
 OutputStreamWriter oWriter = new OutputStreamWriter(fos,"UTF-8");
 //这个地方对流的编码不可或缺,使用main()单独调用时,应该可以,但是如果是web请求导出时导出后word文档就会打不开,并且包XML文件错误。主要是编码格式不正确,无法解析。
 //out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"UTF-8"));
 out = new BufferedWriter(oWriter);
 } catch (FileNotFoundException e1) {
 e1.printStackTrace();
 }
 try {
 //将数据填入模板文件,并输出到目标地址
 t.process(dataMap, out);
 out.close();
 fos.close();
 } catch (TemplateException e) {
 e.printStackTrace();
 } catch (IOException e) {
 e.printStackTrace();
 }
 //System.out.println("---------------------------");
 }
}
public static void main(String[] args) throws UnsupportedEncodingException {
 Map dataMap = new HashMap();
 dataMap.put("xytitle", "试卷");
 int index = 1;
 // 选择题
 List list1 = new ArrayList();//题目
 List list11 = new ArrayList();//答案
 index = 1;
 for (int i = 0; i < 3; i++) {
 Map map = new HashMap();
 map.put("xzn", index + ".");
 map.put("xztest",
 "( )操作系统允许在一台主机上同时连接多台终端,多个用户可以通过各自的终端同时交互地使用计算机。");
 map.put("ans1", "A" + index);
 map.put("ans2", "B" + index);
 map.put("ans3", "C" + index);
 map.put("ans4", "D" + index);
 list1.add(map);
 Map map1 = new HashMap();
 map1.put("fuck", index + ".");
 map1.put("abc", "A" + index);
 list11.add(map1);
 index++;
 }
 dataMap.put("table1", list1);
 dataMap.put("table11", list11);
 // 填空题
 List list2 = new ArrayList();
 List list12 = new ArrayList();
 index = 1;
 for (int i = 0; i < 2; i++) {
 Map map = new HashMap();
 map.put("tkn", index + ".");
 map.put("tktest",
 "操作系统是计算机系统中的一个___系统软件_______,它管理和控制计算机系统中的___资源_________.");
 list2.add(map);
 Map map1 = new HashMap();
 map1.put("fill", index + ".");
 map1.put("def", "中级调度" + index);
 list12.add(map1);
 index++;
 }
 dataMap.put("table2", list2);
 dataMap.put("table12", list12);
 // 判断题
 List list3 = new ArrayList();
 List list13 = new ArrayList();
 index = 1;
 for (int i = 0; i < 3; i++) {
 Map map = new HashMap();
 map.put("pdn", index + ".");
 map.put("pdtest",
 "复合型防火墙防火墙是内部网与外部网的隔离点,起着监视和隔绝应用层通信流的作用,同时也常结合过滤器的功能。");
 list3.add(map);
 Map map1 = new HashMap();
 map1.put("judge", index + ".");
 map1.put("hij", "对" + index);
 list13.add(map1);
 index++;
 }
 dataMap.put("table3", list3);
 dataMap.put("table13", list13);
 // 简答题
 List list4 = new ArrayList();
 List list14 = new ArrayList();
 index = 1;
 for (int i = 0; i < 2; i++) {
 Map map = new HashMap();
 map.put("jdn", index + ".");
 map.put("jdtest", "说明作业调度,中级调度和进程调度的区别,并分析下述问题应由哪一级调度程序负责。");
 list4.add(map);
 Map map1 = new HashMap();
 map1.put("answer", index + ".");
 map1.put("xyz", "说明作业调度,中级调度和进程调度的区别,并分析下述问题应由哪一级调度程序负责。");
 list14.add(map1);
 index++;
 }
 dataMap.put("table4", list4);
 dataMap.put("table14", list14);
 DocumentHandler mdoc = new DocumentHandler();
 mdoc.createDoc(dataMap, "E:/excel/考题.docx");
 }
  • 注意上面 map 中的 key 必须和模板中的相对应,否则会报错。导出效果如下:

好事定律:每件事最后都会是好事,如果不是好事,说明还没到最后。

作者:Cike___原文地址:https://blog.csdn.net/Cike___/article/details/136045075

%s 个评论

要回复文章请先登录注册