在之前的文章中,记录了如何使用freemarker根据word模板生成word文档,但是最终生成的word文档有局限性。
在项目当中除了需要生成word之外,还需要将word通过浏览器预览给用户观看,于是想到了将word转成HTML、将word转成PDF、使用微软Office Online预览,但是在转换或者预览freemarker生成的文档时发生了转换错误:word转HTML时HTML页面只显示XML文档、word转PDF时提示格式不正确、OfficeOnline不支持预览,这个是为什么呢? 原来是因为在使用freemarker生成word时并不是完全符合word的格式,于是会造成转换失败的问题。
既然不能使用freemarker,那用什么来生成word呢,经过搜索有很多的方式,比如POI、openoffice等。
本文章主要记录使用POI根据word模板创建word文档的过程,主要借鉴自https://blog.csdn.net/weixin_43171019/article/details/94384900
由于项目没有要求使用页眉、页脚、图片等所以本文只是简化了代码,如有需要可以在原文章中查看。
接下来开始上代码:
在测试过程中使用maven进行管理所以复制dependency即可:(注意jar版本需要一致)
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml-schemas -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.15</version>
</dependency>
package com.jtexplorer.utils;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import java.io.*;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class WordReporter {
private String tempLocalPath;
private XWPFDocument xwpfDocument = null;
private FileInputStream inputStream = null;
private OutputStream outputStream = null;
public WordReporter(){
}
public WordReporter(String tempLocalPath){
this.tempLocalPath = tempLocalPath;
}
/**
* 设置模板路径
* @param tempLocalPath
*/
public void setTempLocalPath(String tempLocalPath) {
this.tempLocalPath = tempLocalPath;
}
/**
* 初始化
* @throws IOException
*/
public void init() throws IOException{
inputStream = new FileInputStream(new File(this.tempLocalPath));
xwpfDocument = new XWPFDocument(inputStream);
}
/**
* 导出方法
* @param params 表格里的数据列表
* @param orgFullName 需要替换的页眉的公司的名称
* @param logoFilePath 需要替换的页眉的Logo的图片的地址
* @param tableIndex 需替换的第几个表格的下标
* @param textMap 需替换的文本的数据入参
* @return
* @throws Exception
*/
public boolean export(List<Map<String,String>> params,String orgFullName,String logoFilePath, int tableIndex,Map<String,String> textMap) throws Exception{
//createWaterMark(xwpfDocument);
createHeader(xwpfDocument,orgFullName,logoFilePath);
replaceText(xwpfDocument,textMap);
this.insertValueToTable(xwpfDocument,params,tableIndex);
return true;
}
//添加水印 本示例没有用到水印 如果想添加水印 可以在导出方法里调用此方法
public void createWaterMark(XWPFDocument doc){
XWPFHeaderFooterPolicy policy=doc.getHeaderFooterPolicy();
policy.createWatermark("水印");
}
/**
* 替换非表格埋点值
* @param xwpfDocument
* @param textMap 需要替换的文本入参
*/
public void replaceText(XWPFDocument xwpfDocument,Map<String,String> textMap){
List<XWPFParagraph> paras=xwpfDocument.getParagraphs();
Set<String> keySet=textMap.keySet();
for (XWPFParagraph para : paras) {
//当前段落的属性
System.out.println("打印获取到的段落的每一行数据++++++++>>>>>>>"+para.getText());
String str=para.getText();
System.out.println("========================>>>>>>"+para.getParagraphText());
List<XWPFRun> list=para.getRuns();
for(XWPFRun run:list){
for(String key:keySet){
if(key.equals(run.text())){
run.setText(textMap.get(key),0);
}
}
}
}
}
/**
* 循环填充表格内容
* @param xwpfDocument
* @param params
* @param tableIndex
* @throws Exception
*/
private void insertValueToTable(XWPFDocument xwpfDocument, List<Map<String,String>> params, int tableIndex) throws Exception {
try {
List<XWPFTable> tableList = xwpfDocument.getTables();
if (tableList.size() <= tableIndex) {
throw new Exception("tableIndex对应的表格不存在");
}
XWPFTable table = tableList.get(tableIndex);
List<XWPFTableRow> rows = table.getRows();
if (rows.size() < 2) {
throw new Exception("tableIndex对应表格应该为2行");
}
//模板的那一行
XWPFTableRow tmpRow = rows.get(1);
List<XWPFTableCell> tmpCells = null;
List<XWPFTableCell> cells = null;
XWPFTableCell tmpCell = null;
tmpCells = tmpRow.getTableCells();
String cellText = null;
String cellTextKey = null;
Map<String, Object> totalMap = null;
for (int i = 0, len = params.size(); i < len; i++) {
Map<String, String> map = params.get(i);
// 创建新的一行
XWPFTableRow row = table.createRow();
// 获取模板的行高 设置为新一行的行高
row.setHeight(tmpRow.getHeight());
cells = row.getTableCells();
for (int k = 0, klen = cells.size(); k < klen; k++) {
tmpCell = tmpCells.get(k);
XWPFTableCell cell = cells.get(k);
cellText = tmpCell.getText();
if (StringUtils.isNotBlank(cellText)) {
//转换为mapkey对应的字段
cellTextKey = cellText.replace("$", "").replace("{", "").replace("}", "");
if (map.containsKey(cellTextKey)) {
// 填充内容 并且复制模板行的属性
setCellText(tmpCell, cell, map.get(cellTextKey));
}
}
}
}
// 删除模版行
table.removeRow(1);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 复制模板行的属性
* @param tmpCell
* @param cell
* @param text
* @throws Exception
*/
private void setCellText(XWPFTableCell tmpCell, XWPFTableCell cell,String text) throws Exception {
CTTc cttc2 = tmpCell.getCTTc();
CTTcPr ctPr2 = cttc2.getTcPr();
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
if (ctPr2.getTcW() != null) {
ctPr.addNewTcW().setW(ctPr2.getTcW().getW());
}
if (ctPr2.getVAlign() != null) {
ctPr.addNewVAlign().setVal(ctPr2.getVAlign().getVal());
}
if (cttc2.getPList().size() > 0) {
CTP ctp = cttc2.getPList().get(0);
if (ctp.getPPr() != null) {
if (ctp.getPPr().getJc() != null) {
cttc.getPList().get(0).addNewPPr().addNewJc()
.setVal(ctp.getPPr().getJc().getVal());
}
}
}
if (ctPr2.getTcBorders() != null) {
ctPr.setTcBorders(ctPr2.getTcBorders());
}
XWPFParagraph tmpP = tmpCell.getParagraphs().get(0);
XWPFParagraph cellP = cell.getParagraphs().get(0);
XWPFRun tmpR = null;
if (tmpP.getRuns() != null && tmpP.getRuns().size() > 0) {
tmpR = tmpP.getRuns().get(0);
}
XWPFRun cellR = cellP.createRun();
cellR.setText(text);
// 复制字体信息
if (tmpR != null) {
if(!cellR.isBold()){
cellR.setBold(tmpR.isBold());
}
cellR.setItalic(tmpR.isItalic());
cellR.setUnderline(tmpR.getUnderline());
cellR.setColor(tmpR.getColor());
cellR.setTextPosition(tmpR.getTextPosition());
if (tmpR.getFontSize() != -1) {
cellR.setFontSize(tmpR.getFontSize());
}
if (tmpR.getFontFamily() != null) {
cellR.setFontFamily(tmpR.getFontFamily());
}
if (tmpR.getCTR() != null) {
if (tmpR.getCTR().isSetRPr()) {
CTRPr tmpRPr = tmpR.getCTR().getRPr();
if (tmpRPr.isSetRFonts()) {
CTFonts tmpFonts = tmpRPr.getRFonts();
CTRPr cellRPr = cellR.getCTR().isSetRPr() ? cellR
.getCTR().getRPr() : cellR.getCTR().addNewRPr();
CTFonts cellFonts = cellRPr.isSetRFonts() ? cellRPr
.getRFonts() : cellRPr.addNewRFonts();
cellFonts.setAscii(tmpFonts.getAscii());
cellFonts.setAsciiTheme(tmpFonts.getAsciiTheme());
cellFonts.setCs(tmpFonts.getCs());
cellFonts.setCstheme(tmpFonts.getCstheme());
cellFonts.setEastAsia(tmpFonts.getEastAsia());
cellFonts.setEastAsiaTheme(tmpFonts.getEastAsiaTheme());
cellFonts.setHAnsi(tmpFonts.getHAnsi());
cellFonts.setHAnsiTheme(tmpFonts.getHAnsiTheme());
}
}
}
}
// 复制段落信息
cellP.setAlignment(tmpP.getAlignment());
cellP.setVerticalAlignment(tmpP.getVerticalAlignment());
cellP.setBorderBetween(tmpP.getBorderBetween());
cellP.setBorderBottom(tmpP.getBorderBottom());
cellP.setBorderLeft(tmpP.getBorderLeft());
cellP.setBorderRight(tmpP.getBorderRight());
cellP.setBorderTop(tmpP.getBorderTop());
cellP.setPageBreak(tmpP.isPageBreak());
if (tmpP.getCTP() != null) {
if (tmpP.getCTP().getPPr() != null) {
CTPPr tmpPPr = tmpP.getCTP().getPPr();
CTPPr cellPPr = cellP.getCTP().getPPr() != null ? cellP
.getCTP().getPPr() : cellP.getCTP().addNewPPr();
// 复制段落间距信息
CTSpacing tmpSpacing = tmpPPr.getSpacing();
if (tmpSpacing != null) {
CTSpacing cellSpacing = cellPPr.getSpacing() != null ? cellPPr
.getSpacing() : cellPPr.addNewSpacing();
if (tmpSpacing.getAfter() != null) {
cellSpacing.setAfter(tmpSpacing.getAfter());
}
if (tmpSpacing.getAfterAutospacing() != null) {
cellSpacing.setAfterAutospacing(tmpSpacing
.getAfterAutospacing());
}
if (tmpSpacing.getAfterLines() != null) {
cellSpacing.setAfterLines(tmpSpacing.getAfterLines());
}
if (tmpSpacing.getBefore() != null) {
cellSpacing.setBefore(tmpSpacing.getBefore());
}
if (tmpSpacing.getBeforeAutospacing() != null) {
cellSpacing.setBeforeAutospacing(tmpSpacing
.getBeforeAutospacing());
}
if (tmpSpacing.getBeforeLines() != null) {
cellSpacing.setBeforeLines(tmpSpacing.getBeforeLines());
}
if (tmpSpacing.getLine() != null) {
cellSpacing.setLine(tmpSpacing.getLine());
}
if (tmpSpacing.getLineRule() != null) {
cellSpacing.setLineRule(tmpSpacing.getLineRule());
}
}
// 复制段落缩进信息
CTInd tmpInd = tmpPPr.getInd();
if (tmpInd != null) {
CTInd cellInd = cellPPr.getInd() != null ? cellPPr.getInd()
: cellPPr.addNewInd();
if (tmpInd.getFirstLine() != null) {
cellInd.setFirstLine(tmpInd.getFirstLine());
}
if (tmpInd.getFirstLineChars() != null) {
cellInd.setFirstLineChars(tmpInd.getFirstLineChars());
}
if (tmpInd.getHanging() != null) {
cellInd.setHanging(tmpInd.getHanging());
}
if (tmpInd.getHangingChars() != null) {
cellInd.setHangingChars(tmpInd.getHangingChars());
}
if (tmpInd.getLeft() != null) {
cellInd.setLeft(tmpInd.getLeft());
}
if (tmpInd.getLeftChars() != null) {
cellInd.setLeftChars(tmpInd.getLeftChars());
}
if (tmpInd.getRight() != null) {
cellInd.setRight(tmpInd.getRight());
}
if (tmpInd.getRightChars() != null) {
cellInd.setRightChars(tmpInd.getRightChars());
}
}
}
}
}
//添加不带图片的页眉 如果页眉不做图片处理 可以用此方法
public void createHeader(XWPFDocument doc, String orgFullName) throws Exception {
/*
* 对页眉段落作处理
* */
CTSectPr sectPr = doc.getDocument().getBody().addNewSectPr();
XWPFHeaderFooterPolicy headerFooterPolicy = new XWPFHeaderFooterPolicy(doc,sectPr);
XWPFHeader header = headerFooterPolicy.createHeader(XWPFHeaderFooterPolicy.DEFAULT);
XWPFParagraph paragraph = header.getParagraphArray(0);
paragraph.setBorderBottom(Borders.THICK);
/*
* 添加字体页眉,公司全称
* */
if (StringUtils.isNotEmpty(orgFullName)) {
XWPFRun run = paragraph.createRun();
run.setText(orgFullName);
//setXWPFRunStyle(run,"新宋体",10);
}
}
/**
* 对页眉处理 页眉包含公司名称 二维码图片
* @param doc
* @param orgFullName 公司全称
* @param logoFilePath 二维码图片的地址
* @throws Exception
*/
public void createHeader(XWPFDocument doc, String orgFullName, String logoFilePath) throws Exception {
/*
* 对页眉段落作处理,使二维码图片在页眉右边,公司全称在页眉左边
* */
CTSectPr sectPr = doc.getDocument().getBody().addNewSectPr();
XWPFHeaderFooterPolicy headerFooterPolicy = new XWPFHeaderFooterPolicy(doc, sectPr);
XWPFHeader header = headerFooterPolicy.createHeader(XWPFHeaderFooterPolicy.DEFAULT);
XWPFParagraph paragraph = header.getParagraphArray(0);
paragraph.setAlignment(ParagraphAlignment.BOTH); //设置段落左对齐
paragraph.setBorderBottom(Borders.THICK); //设置下划线
XWPFRun run = paragraph.createRun();
setXWPFRunStyle(run,"新宋体",10);
/*
* 添加字体页眉,公司全称
* 公司全称在右边
* */
if (StringUtils.isNotEmpty(orgFullName)) {
run = paragraph.createRun();
run.setText(orgFullName);
setXWPFRunStyle(run,"新宋体",10);
}
//处理两个段落之间的制表符宽度
run.addTab();
CTTabStop tabStop = paragraph.getCTP().getPPr().addNewTabs().addNewTab();
tabStop.setVal(STTabJc.CENTER);
int twipsPerInch = 1450;
tabStop.setPos(BigInteger.valueOf(6 * twipsPerInch));
/**
* 设置页脚的字体样式
*
* @param r1 段落元素
*/
private void setXWPFRunStyle(XWPFRun r1,String font,int fontSize) {
r1.setFontSize(fontSize);
CTRPr rpr = r1.getCTR().isSetRPr() ? r1.getCTR().getRPr() : r1.getCTR().addNewRPr();
CTFonts fonts = rpr.isSetRFonts() ? rpr.getRFonts() : rpr.addNewRFonts();
fonts.setAscii(font);
fonts.setEastAsia(font);
fonts.setHAnsi(font);
}
/**
* 收尾方法
* @param outDocPath
* @return
* @throws IOException
*/
public boolean generate(String outDocPath) throws IOException{
outputStream = new FileOutputStream(outDocPath);
xwpfDocument.write(outputStream);
this.close(outputStream);
this.close(inputStream);
return true;
}
/**
* 关闭输入流
* @param is
*/
private void close(InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 关闭输出流
* @param os
*/
private void close(OutputStream os) {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
private String exportExamCost(Integer project){
ExamProject examProject = examProjectService.getExamProjectById(project);
if(examProject != null){
Double cost = examProject.getUserPrice();
Map<String,String> textMap=new HashMap<>();
textMap.put("projectName", examProject.getProjectName());
Map<String,String> param = new HashMap<>();
List<ExamProjectGroupUser> examProjectGroupUserList = examProjectGroupUserService.getListByJdbc(param);
List<Map<String, String>> mapList = new ArrayList<>();
Map<String, String> map = null;
if(examProjectGroupUserList != null && examProjectGroupUserList.size() > 0){
double count = examProjectGroupUserList.size() / 2.0;
for (int i = 0; i <count; i++) {
map = new HashMap<>();
map.put("userName1",examProjectGroupUserList.get(i).getUserName());
map.put("cost1",cost.toString());
double floor = Math.ceil(examProjectGroupUserList.size() - i) / 2.0;
int floorInt = (new Double(floor)).intValue() -1;
map.put("userName2",examProjectGroupUserList.get(floorInt).getUserName());
map.put("cost2",cost.toString());
mapList.add(map);
}
}
String filePath ="/Users/xuwang/Desktop/examCost.docx";
String outPath ="/Users/xuwang/Desktop/examCostTemplate.docx";
String imgFile="";
WordReporter wordReporter = new WordReporter();
wordReporter.setTempLocalPath(filePath); //设置模板的路径
try {
wordReporter.init(); //初始化工具类
} catch (IOException e) {
e.printStackTrace();
}
try {
wordReporter.export(mapList,examProject.getProjectName(),imgFile,0,textMap); //写入相关数据
} catch (Exception e) {
e.printStackTrace();
}
try {
wordReporter.generate(outPath);
} catch (IOException e) {
e.printStackTrace();
}
}
return "";
}
finally: 效果
{{ cmt.username }}
{{ cmt.content }}
{{ cmt.commentDate | formatDate('YYYY.MM.DD hh:mm') }}