前言
poi-tl(poi template language) 是word的模板引擎,使用poi-tl生成的word文档中生成表格、段落、图片等 都可以。 在文档的任何地方做任何事情(Do Anything Anywhere)是poi-tl的星辰大海。
- 创建文本的基本概念
- XWPFDocument代表一个docx文档,其可以用来读docx文档,也可以用来写docx文档
- XWPFParagraph代表文档、表格、标题等种的段落,由多个XWPFRun组成
- XWPFRun代表具有同样风格的一段文本
- XWPFTable代表一个表格
- XWPFTableRow代表表格的一行
- XWPFTableCell代表表格的一个单元格
模板--word模板
- 这是我创建.docx 文件的模板,把它作为模板将数据依次填写到对应的表格中
- 导出之后的模板如下图,接下来我将安装我的模板的内容依次说明文件是怎么生成的,数据是怎么写在模板中的
段落的生成
段落的生成可以使用俩中方法来实现,一种是有匹配的内容,另一种是没有匹配的内容,但是在开头有一个标志。
1.${}包裹
这种的核心思想就是获取段落中的文字是否被${}包裹着,如果被包裹,则替换当前的文字
/**
* 匹配传入信息集合与模板
* @param value 模板需要替换的区域
* @param textMap 传入信息集合
* @return 模板需要替换区域信息集合对应值
*/
public static String replaceWordContent(String value, Map<String, String> textMap){
Set<Entry<String, String>> textSets = textMap.entrySet();
for (Entry<String, String> textSet : textSets) {
//匹配模板与替换值 格式${key}
String key = "${"+textSet.getKey()+"}";
if(value.contains(key)){
value = textSet.getValue();
}
}
//模板未匹配到区域替换为空
if(checkText(value)){
value = "";
}
return value;
}
复制代码
2.开头有一个标志
开头有一个标志 说的是在段落的开头需要有一个标志,作为当检索到这个标志的时候,开始在文档上写入内容,如下图中的开始就作为一个标志
private void iteratorParagraph(Map<String,Object> params,XWPFDocument document) throws IOException{
// 创建段落
XWPFParagraph graph = null;
// 在文档的基础上创建一个段落
graph = document.createParagraph();
List<XWPFParagraph> paragraphs = document.getParagraphs();
for(int i =0;i<paragraphs.size();i++){
// 获取每一个段落的内容
XWPFParagraph graph1= paragraphs.get(i);
//这里的“开始”就是一个标志
if(graph1.getText().equals("开始")){
// 在一个段落中新加一行
XWPFRun row1 = graph.createRun();
//addCarriageReturn()换行,row.addBreak()——这个也是换行
row1.addCarriageReturn();
// 省略很多内容.......
String str = "";
for (String k : params.keySet()){
if(k == "description"){
Object obj = params.get(k);
str = obj.toString();
break;
}
}
StringBuilder stringBuilder = new StringBuilder();
for(int j = 0; j< 64-str.length();j++){
// 当后面不足时,添加空格内容
stringBuilder.append("");
}
document.removeBodyElement(document.getPosOfParagraph(graph1));
}
}
}
复制代码
表格的内容
表格中有
${}
当模板中有${}包裹的时候,使用Map<String,String> textMap
来存储键值对,当key == 模板表格中的值,便将Value 写在当前的单元格中。
/**
* 遍历表格
* @param rows 表格行对象
* @param textMap 需要替换的信息集合
*/
private static void replaceWordTableContent(List<XWPFTableRow> rows ,Map<String, String> textMap){
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
//判断单元格是否需要替换
// checkText()判断表格模板中是否被${}包裹
if(checkText(cell.getText())){
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//replaceWordContent() 遍历Map,依次取出Map 中的key和value
run.setText(replaceWordContent(run.toString(), textMap),0);
}
}
}
}
}
}
复制代码
表格中没有
${}
当表格中没有${}包裹,获取到空的单元格,使用遍历行和列,获取到单元格cell,同时调用cell.setText()
的方法,依次将数据写入到对应的单元格中。
// 遍历文档中的表格
private void iteratorTable(Map<String,Object> params,XWPFDocument document) throws IOException{
List<XWPFTableRow> rows = null;
List<XWPFTableCell> cells = null;
// 获取文档中的表格内容
List<XWPFTable> tables = document.getTables();
for(XWPFTable table : tables){
rows = table.getRows();
// 遍历表格中的所有的行
for(XWPFTableRow row : rows){
cells = row.getTableCells();
// 遍历这一行的单元格
for(XWPFTableCell cell : cells){
// 判断改单元格的内容是否是字符串字段
if(strMatcher(cell.getText()).find()){
//replaceInStr()替换字符串,可以多行也可以一行
replaceInStr(cell,params,document);
continue;
}
}
}
}
}
复制代码
- 动态生成表格行数
这是我在上班的时候遇到的一个情景,一起记录子这里: 获取到一个动态的list 数据,就是当行和列不固定时,需要根据实际需求来动态生成行的内容,核心思路就是判断当前list(List<String[]> tableList
)的长度来动态增加行,
/**
* 为表格插入数据,行数不够添加新行
* @param table 需要插入数据的表格
* @param tableList 插入数据集合
*/
private static void insertTable(XWPFTable table, List<String[]> tableList){
//创建行,根据需要插入的数据添加新行,不处理表头
for(int i = 1; i < tableList.size(); i++){
XWPFTableRow row =table.createRow();
}
//遍历表格插入数据
List<XWPFTableRow> rows = table.getRows();
for(int i = 1; i < rows.size(); i++){
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
for(int j = 0; j < cells.size(); j++){
XWPFTableCell cell = cells.get(j);
cell.setText(tableList.get(i-1)[j]);
}
}
}
复制代码
- 跨行合并
当数据传递的数据为空时,需要显示一个暂无参数,同时合并单元格
/**
* 合并列
* @param table
* @param rowNumber 需要合并的列在的行
* @param fromCol 开始列
* @param toCol 结束列
*/
private static void mergeColumns(XWPFTable table, int rowNumber, int fromCol, int toCol) {
XWPFTableRow row = table.getRow(rowNumber);
for(int index = fromCol; index < toCol; index++){
CTHMerge hMerge = CTHMerge.Factory.newInstance();
if(index == fromCol){
hMerge.setVal(STMerge.RESTART);
} else {
hMerge.setVal(STMerge.CONTINUE);
}
XWPFTableCell cell = row.getCell(index);
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr != null) {
tcPr.setHMerge(hMerge);
} else {
tcPr = CTTcPr.Factory.newInstance();
tcPr.setHMerge(hMerge);
cell.getCTTc().setTcPr(tcPr);
}
}
}
复制代码
- 跨列合并
/**
* 当接口无参数时,合并行
* @param table 当前表
* @param col 需要合并的行
* @param fromRow 开始行
* @param toRow 结束行
* */
private static void mergeRow(XWPFTable table, int col, int fromRow, int toRow){
//index 为 行索引
for (int index = fromRow; index <= toRow ; index++) {
// 重新创建类加载---类似于new()
CTVMerge merge = CTVMerge.Factory.newInstance();
if(index == fromRow){
merge.setVal(STMerge.RESTART);
}else{
merge.setVal(STMerge.CONTINUE);
}
XWPFTableCell tableCell = table.getRow(index).getCell(col);
CTTcPr tcPr = tableCell.getCTTc().getTcPr();
if(tcPr != null){
tcPr.setVMerge(merge);
}else{
tcPr = CTTcPr.Factory.newInstance();
tcPr.setVMerge(merge);
tableCell.getCTTc().setTcPr(tcPr);
}
}
}
复制代码
补充
设置字体和字号
List<XWPFParagraph> graphs = cell.getParagraphs();
for(XWPFParagraph graph : graphs){
XWPFRun run = graph.createRun();
run.setFontSize(12);
run.setFontFamily("宋体");
run.setText(tableList.get(i-1)[j]);
}
复制代码
设置表格中的内容的水平和垂直对齐方式
// 输入的内容垂直居中
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
// 设置水平居中
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
复制代码
最后
这篇文章是我看了网上好多博主的文章之后,自己测试之后摸索总结出来,希望对你也有帮助。留下官方文档,值得看看。
这篇文章中用到的maven依赖有:
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.3</version>
</dependency>
复制代码
实现的功能的所有代码
// 导入的相关的包文件
import com.szht.mapper.project.InterfaceMapper;
import com.szht.model.entity.project.InterfaceEntity;
import com.szht.model.entity.project.InterfaceWordEntity;
import com.szht.service.project.ExportWordInterfaceService;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
复制代码
/** 根据interfaceEntity,重写导出word的方法*/
@Override
public void exportWord(InterfaceEntity interfaceEntity, HttpServletResponse response)throws IOException{
//查询业务参数的内容
List<InterfaceWordEntity> interfaceWordEntity = getWordInterfaceById(interfaceEntity.getId());
String path = new File("src\\main\\resources\\templates\\temp.docx").getAbsolutePath();
//导出的名字
String fileName = interfaceEntity.getName() +".docx";
// 在文档中的段落写入文字
Map<String,String> textMap=new HashMap<>();
textMap.put("name",interfaceEntity.getName());
textMap.put("fullPath",interfaceEntity.getFullPath());
textMap.put("updateTime",interfaceEntity.getUpdateTime());
if(interfaceEntity.getDescription() != null){
textMap.put("description",interfaceEntity.getDescription());
}
List<String[]> tableList = new ArrayList<>();
// 当数据集参数大于0时们将数据依次,不为空
if(interfaceWordEntity.size() > 0){
for(InterfaceWordEntity paramEntity : interfaceWordEntity) {
tableList.add(new String[]{paramEntity.getInterfaceId(),paramEntity.getRequiredParameter(),paramEntity.getParamType(),paramEntity.getRequired().toString(),paramEntity.getParamTips()});
}
}else{
// 当数据集参数大于等于0,参数为空的时候,将导出的参数列表合并单元格并且显示”暂无参数“
tableList.add(new String[]{"暂无参数","","","",""});
}
// 给模板中的表格生成默认的数据内容
List<String[]> defaultTableList = new ArrayList<>();
defaultTableList.add(new String[]{"code","String","是","200:查询成功,其他:异常"});
defaultTableList.add(new String[]{"message","String","是","返回信息"});
defaultTableList.add(new String[]{"total","String","是","数据件数"});
defaultTableList.add(new String[]{"data","List<Map<String, String>>","是","返回数据"});
// 表格中的数据使用params,段落中的内容使用 testMap
exportWordTemp(path,textMap,tableList,defaultTableList ,fileName,response);
}
复制代码
// 使用Stream 读取和写入文件内容
public static void exportWordTemp(String inputPath, Map<String, String> textMap, List<String[]> tableList,List<String[]> defaultTableList,String fileName,HttpServletResponse response) {
try {
InputStream inputStream = new FileInputStream(inputPath);
//获取文档XX.docx
XWPFDocument document = new XWPFDocument(inputStream);
// 替换文本对象
replaceText(document,textMap);
// 替换表格内容
replaceTable(document,textMap,tableList,defaultTableList);
OutputStream outputStream = response.getOutputStream();
// 设置导出的内容是doc
response.setContentType("application/octet-stream; charset=utf-8");
response.setHeader("Content-Disposition", "attachment; fileName=" + fileName);
// 将文件写入关闭流
document.write(outputStream);
outputStream.flush();
document.close();
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
/**
*替换段落文本
* @param document docx解析对象
* @param textMap 需要替换的信息集合
* */
private static void replaceText(XWPFDocument document,Map<String,String> textMap){
// 获取段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
// 取每一个段落
for(XWPFParagraph paragraph :paragraphs){
// 判断段落是否需要替换
String text = paragraph.getText();
if(checkText(text)){
List<XWPFRun> runs = paragraph.getRuns();
for(XWPFRun run : runs){
// 替换模板中的内容
run.setText(replaceWordContent(run.toString(),textMap),0);
}
}
}
}
复制代码
/**
* 判断文本中时候包含$
* @param text 文本
* @return 包含返回true,不包含返回false
*/
public static boolean checkText(String text){
return text.contains("$");
}
复制代码
/**
* 匹配传入信息集合与模板
* @param value 模板需要替换的区域
* @param textMap 传入信息集合
* @return 模板需要替换区域信息集合对应值
*/
public static String replaceWordContent(String value, Map<String, String> textMap){
Set<Entry<String, String>> textSets = textMap.entrySet();
for (Entry<String, String> textSet : textSets) {
//匹配模板与替换值 格式${key}
String key = "${"+textSet.getKey()+"}";
if(value.contains(key)){
value = textSet.getValue();
}
}
//模板未匹配到区域替换为空
if(checkText(value)){
value = "";
}
return value;
}
复制代码
/**
* 替换表格中的内容
*@param document docx解析对象
*@param textMap 需要替换的信息集合
*@param tableList 需要插入的表格信息集合
* */
private static void replaceTable(XWPFDocument document,Map<String,String> textMap,List<String[]> tableList,List<String[]> defaultTableList){
// 获取表格对象集合
List<XWPFTable> tables = document.getTables();
for(int i = 0;i < tables.size();i++){
// 只处理行数大于等于2的表格,且不处理表头
XWPFTable table = tables.get(i);
if(table.getRows().size() > 1){
// 当表格中有$替换数据,没有则插入数据
if(checkText(table.getText())){
List<XWPFTableRow> rows = table.getRows();
// 遍历表格,并替换模板
replaceWordTableContent(rows,textMap);
}else {
// 当表格大于模板的行数时,新增行数
// tables.get(2) == table <=> i ==2
if(i == 2){
// 当时第三张表的数据时,则添加默认数据
insertTable(tables,table,defaultTableList);
}else if(i == 1 && tableList.get(0)[0] == "暂无参数"){
for (int j = 0; j < tableList.size(); j++) {
mergeColumns(table,j+1,0,5);
}
insertTable(tables,table,tableList);
}else {
insertTable(tables,table,tableList);
}
}
}
}
}
复制代码
/**
* 遍历表格
* @param rows 表格行对象
* @param textMap 需要替换的信息集合
*/
private static void replaceWordTableContent(List<XWPFTableRow> rows ,Map<String, String> textMap){
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
//设置替换的表格中的内容垂直居中
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
//设置替换的表格中的内容水平居中
CTTcPr ctPr = cell.getCTTc().addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cell.getCTTc().getPList().get(0).addNewPPr().addNewJc().setVal(STJc.LEFT);
//判断单元格是否需要替换
if(checkText(cell.getText())){
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
run.setText(replaceWordContent(run.toString(), textMap),0);
paragraph.setVerticalAlignment(TextAlignment.CENTER);
run.setFontFamily("宋体");
run.setFontSize(12);
}
}
}
}
}
}
复制代码
/**
* 为表格插入数据,行数不够添加新行
* @param tables 获取模板中的所有表格,取出其中的一张表
* @param table 需要插入数据的表格
* @param tableList 插入数据集合
*/
private static void insertTable(List<XWPFTable> tables,XWPFTable table, List<String[]> tableList){
//创建行,根据需要插入的数据添加新行,不处理表头
for(int i = 1; i < tableList.size(); i++){
XWPFTableRow row =table.createRow();
//对于多个参数的表格,将interfaceID 跨行合并单元格,只对第一张表进行跨单元格合并
if(tables.get(1) == table){
mergeRow(table,0,i,i+1);
}
}
//遍历表格插入数据
List<XWPFTableRow> rows = table.getRows();
for(int i = 1; i < rows.size(); i++){
//为新增的表格设置行高 567 为 1cm
table.getRows().get(i).setHeight(567);
// 设置列宽
// 表格属性
CTTblPr tblPr = table.getCTTbl().addNewTblPr();
//表格宽度
CTTblWidth width = tblPr.addNewTblW();
// 1710 :大约为3cm
width.setW(BigInteger.valueOf(1710));
//设置表格宽度为非自动
// width.setType(STTblWidth.DXA);
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
for(int j = 0; j < cells.size(); j++){
XWPFTableCell cell = cells.get(j);
// 输入的内容垂直居中
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
// 设置水平居中
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
List<XWPFParagraph> graphs = cell.getParagraphs();
for(XWPFParagraph graph : graphs){
XWPFRun run = graph.createRun();
run.setFontSize(12);
run.setFontFamily("宋体");
run.setText(tableList.get(i-1)[j]);
}
}
}
}
复制代码
/**
* 当接口无参数时,合并行
* @param table 当前表
* @param col 需要合并的行
* @param fromRow 开始行
* @param toRow 结束行
* */
private static void mergeRow(XWPFTable table, int col, int fromRow, int toRow){
//index 为 行索引
for (int index = fromRow; index <= toRow ; index++) {
// 重新创建类加载---类似于new()
CTVMerge merge = CTVMerge.Factory.newInstance();
if(index == fromRow){
merge.setVal(STMerge.RESTART);
}else{
merge.setVal(STMerge.CONTINUE);
}
XWPFTableCell tableCell = table.getRow(index).getCell(col);
CTTcPr tcPr = tableCell.getCTTc().getTcPr();
if(tcPr != null){
tcPr.setVMerge(merge);
}else{
tcPr = CTTcPr.Factory.newInstance();
tcPr.setVMerge(merge);
tableCell.getCTTc().setTcPr(tcPr);
}
}
}
复制代码
/**
* 合并列
* @param table
* @param rowNumber 需要合并的列在的行
* @param fromCol 开始列
* @param toCol 结束列
*/
private static void mergeColumns(XWPFTable table, int rowNumber, int fromCol, int toCol) {
XWPFTableRow row = table.getRow(rowNumber);
for(int index = fromCol; index < toCol; index++){
CTHMerge hMerge = CTHMerge.Factory.newInstance();
if(index == fromCol){
hMerge.setVal(STMerge.RESTART);
} else {
hMerge.setVal(STMerge.CONTINUE);
}
XWPFTableCell cell = row.getCell(index);
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr != null) {
tcPr.setHMerge(hMerge);
} else {
tcPr = CTTcPr.Factory.newInstance();
tcPr.setHMerge(hMerge);
cell.getCTTc().setTcPr(tcPr);
}
}
}
复制代码