使用 Poi-tl 处理 Word 文档表格数据
根据模板文件,替换模板文件里对应的数据,这里使用 poi-tl 进行处理。
poi-tl 是在 poi 的基础上进行封装的,因此使用 poi-tl 时,注意彼此版本对应关系。
引入架包
<!--poi框架-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.2</version>
</dependency>
<!--注意对应poi的版本依赖:https://deepoove.com/poi-tl/ -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
核心代码
//核心代码
ConfigureBuilder builder = Configure.builder();
try (XWPFTemplate xwpfTemplate = XWPFTemplate.compile(file.getInputStream(), builder.build()).render(
new HashMap<String, Object>() {{
mapCode.forEach(this::put);
}})) {
// 将完成数据渲染的文档写入到输入流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
xwpfTemplate.write(byteArrayOutputStream);
// 将输入流转成 multipartFile
file = fileService.createByteMultipartFile(byteArrayOutputStream, name, name);
} catch (Exception e) {
setRecord(dto, template, null, 1, "生成文件时发生异常: " + e.getMessage());
throw new Exception("生成文件时发生异常: " + e.getMessage());
}
前端数据
{
"mapCode": {
"ID_dizhi": "CNT20150011",
"ID_hetongbianhao": "14875.25",
"ID_riqi": "2024-04-01",
"ID_tianbiaoriqi": "2024-04-01"
}
}
Word 模板
在 Word 模板中,只需要在对应的位置添加如下变量形式即可: ,那么最后生成的文件里,变量的位置就不被
CNT20150011
替换。
表格数据
表格数据,这里使用动态表格类:DynamicTableRenderPolicy 进行处理。
定义处理表格的类
**
* 定义处理表格的插件
*/
public class DetailTablePolicy extends DynamicTableRenderPolicy {
@Override
public void render(XWPFTable table, Object object) throws Exception {
if (null == object) {
return;
}
try {
// 获取数据
LinkedHashMap<String, Object> mapValue = (LinkedHashMap<String, Object>) object;
// 添加表头数据,默认表格第一行为表头
XWPFTableRow headerRow = table.getRow(0);
int columnIndex = 0;
for (String columnName : mapValue.keySet()) {
XWPFTableCell headerCell = headerRow.getCell(columnIndex);
// 设置垂直居中
headerCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
XWPFParagraph paragraph = headerCell.getParagraphArray(0);
paragraph.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = paragraph.createRun();
// 设置大小,加粗和列名
run.setFontSize(11);
run.setBold(true);
run.setText(columnName);
columnIndex++;
}
// 获取列数
int size = 0;
for (Object value : mapValue.values()) {
if (value instanceof List) {
size = ((List) value).size();
break;
}
break;
}
// 先把列的数据转成行数据
List<List<String>> values = new ArrayList<>();
for (int i = 0; i < size; i++) {
List<String> item = new ArrayList<String>();
for (Object ob : mapValue.values()) {
if (ob instanceof List) {
item.add((String) ((List) ob).get(i));
}
}
values.add(item);
}
// 再把行数据按照每行添加进去
int index = 1;
for (List<String> list : values) {
XWPFTableRow row = table.insertNewTableRow(index);
row.setHeight(27*20);
columnIndex = 0;
for (String cellValue : list) {
XWPFTableCell cell = row.createCell();
// 设置整个表格里的数据垂直居中对齐
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
CTTc ctTc = cell.getCTTc();
CTP ctp = ctTc.sizeOfPArray() == 0 ? ctTc.addNewP() : ctTc.getPArray(0);
XWPFParagraph paragraph = cell.getParagraph(ctp);
paragraph.setAlignment(ParagraphAlignment.CENTER);
cell.setText(cellValue);
columnIndex++;
}
index++;
}
} catch (Exception e) {
throw new Exception("模板文件中存在多余的编码!");
}
}
}
定义处理表格方法
ConfigureBuilder builder = Configure.builder();
mapCode.forEach((k, v) -> {
// 设置勾选框
if (v instanceof List) {
StringBuilder str = new StringBuilder();
((List) v).forEach(m -> {
TextRenderData data = new TextRenderData();
data.setText("R" + m.toString());
str.append(data.getText());
});
// 勾选框通过设置字体样式来达到显示效果
mapCode.put(k, new TextRenderData(str.toString(), new Style("Wingdings 2",12)));
}
// 遍历表格数据,new DetailTablePolicy() 为每个表格都使用的插件类
if (v instanceof LinkedHashMap) {
((LinkedHashMap) v).forEach((s, x) -> {
builder.bind(new DetailTablePolicy(), k);
});
}
});
// 把数据渲染到文件中去
MultipartFile file = null;
String name = DateUtil.format(new Date(), "yyyyMMdd") + String.format("%05d", new Random().nextInt(100000)) + ".docx";
MultipartFile multipartFile = fileService.createMultipartFile(resUrl.getData().toString(), name, name);
try (XWPFTemplate xwpfTemplate = XWPFTemplate.compile(multipartFile.getInputStream(), builder.build()).render(
new HashMap<String, Object>() {{
mapCode.forEach(this::put);
}})) {
// 将完成数据渲染的文档写入到输入流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
xwpfTemplate.write(byteArrayOutputStream);
// 将输入流转成 multipartFile
file = fileService.createByteMultipartFile(byteArrayOutputStream, name, name);
} catch (Exception e) {
setRecord(dto, template, null, 1, "生成文件时发生异常: " + e.getMessage());
throw new Exception("生成文件时发生异常: " + e.getMessage());
}
前端数据
// 后端实体类
@ApiModelProperty("编码和填写的数值")
private LinkedHashMap<String, Object> mapCode;
{
"mapCode": {
"ID_zhucheguishu": [
"委托方",
"注册方"
],
"ID_dizhi": "CNT20150011",
"ID_hetongbianhao": "14875.25",
"ID_riqi": "2024-04-01",
"ID_tianbiaoriqi": "2024-04-01",
"ID_renyuanxinxi": {
"序号": [],
"姓名": [],
"性别": [],
"年龄": []
},
"ID_suntances": {
"编号": [
"R1"
],
"物质名称": [
"神经酰胺NP"
],
"吨位": [
"无意义"
]
}
}
}
// ID_zhucheguishu 表示勾选框选择的值
// ID_renyuanxinxi 和 ID_suntances 对应为表格数据
Word 模板
表格的制作,有个前提,那就是列数是确定的。
延伸思考:指定插入的行
上面的例子中,表格的数据都是从第一行插入的,如果模板中,既有从第一行插入的数据,又有不是从第一行插入的数据,那要如何处理。
修改处理表格的类
**
* 定义处理表格的插件
* 与原方法的区别:
* // 增加表头行数位置
* private int rowIndex;
* public DetailTablePolicy(int rowIndex) {this.rowIndex = rowIndex;}
* // 通过传过来的数据决定插入的位置
* XWPFTableRow headerRow = table.getRow(rowIndex);
* // 行数据紧跟表头位置
* int index = rowIndex + 1;
*/
public class DetailTablePolicy extends DynamicTableRenderPolicy {
// 增加表头行数位置
private int rowIndex;
public DetailTablePolicy(int rowIndex) {
this.rowIndex = rowIndex;
}
@Override
public void render(XWPFTable table, Object object) throws Exception {
if (null == object) {
return;
}
try {
// 获取数据
LinkedHashMap<String, Object> mapValue = (LinkedHashMap<String, Object>) object;
// 添加表头数据,根据表头行位置来决定位置
XWPFTableRow headerRow = table.getRow(rowIndex);
int columnIndex = 0;
for (String columnName : mapValue.keySet()) {
XWPFTableCell headerCell = headerRow.getCell(columnIndex);
// 设置垂直居中
headerCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
XWPFParagraph paragraph = headerCell.getParagraphArray(0);
paragraph.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = paragraph.createRun();
// 设置大小,加粗和列名
run.setFontSize(11);
run.setBold(true);
run.setText(columnName);
columnIndex++;
}
// 获取列数
int size = 0;
for (Object value : mapValue.values()) {
if (value instanceof List) {
size = ((List) value).size();
break;
}
break;
}
// 先把列的数据转成行数据
List<List<String>> values = new ArrayList<>();
for (int i = 0; i < size; i++) {
List<String> item = new ArrayList<String>();
for (Object ob : mapValue.values()) {
if (ob instanceof List) {
item.add((String) ((List) ob).get(i));
}
}
values.add(item);
}
// 行数据按紧跟表头位置
int index = rowIndex + 1;
for (List<String> list : values) {
XWPFTableRow row = table.insertNewTableRow(index);
row.setHeight(27*20);
columnIndex = 0;
for (String cellValue : list) {
XWPFTableCell cell = row.createCell();
// 设置整个表格里的数据垂直居中对齐
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
CTTc ctTc = cell.getCTTc();
CTP ctp = ctTc.sizeOfPArray() == 0 ? ctTc.addNewP() : ctTc.getPArray(0);
XWPFParagraph paragraph = cell.getParagraph(ctp);
paragraph.setAlignment(ParagraphAlignment.CENTER);
cell.setText(cellValue);
columnIndex++;
}
index++;
}
} catch (Exception e) {
throw new Exception("模板文件中存在多余的编码!");
}
}
}
定义处理表格方法
// 只需要把原方法中的如下代码修改即可
// 原方法
// 遍历表格数据,new DetailTablePolicy() 为每个表格都使用的插件类
if (v instanceof LinkedHashMap) {
((LinkedHashMap) v).forEach((s, x) -> {
builder.bind(new DetailTablePolicy(), k);
});
}
// 修改后的代码
// 遍历表格数据,new DetailTablePolicy() 为每个表格都使用的插件类
if (v instanceof LinkedHashMap) {
((LinkedHashMap) v).forEach((s, x) -> {
// 这一块处理和前端传值有关系
String[] split = k.split("_");
int i = Integer.parseInt(split[2]) - 1;
builder.bind(new DetailTablePolicy(i), k);
});
}
修改前端数据
// 原前端传值,表格的编码为:ID_renyuanxinxi 和 ID_suntances
{
"mapCode": {
"ID_renyuanxinxi": {
"序号": [],
"姓名": [],
"性别": [],
"年龄": []
},
"ID_suntances": {
"编号": [
"R1"
],
"物质名称": [
"神经酰胺NP"
],
"吨位": [
"无意义"
]
}
}
}
// 现在只需要在其后加上数字,对应数字为表示这个表格的第一行数据插入表格的位置,如:ID_renyuanxinxi_1 就表示从第一行插入;ID_suntances_5 就表示从第五行插入。
// 修改后的前端传值
{
"mapCode": {
"ID_renyuanxinxi_1": {
"序号": [],
"姓名": [],
"性别": [],
"年龄": []
},
"ID_suntances_5": {
"编号": [
"R1"
],
"物质名称": [
"神经酰胺NP"
],
"吨位": [
"无意义"
]
}
}
}
延伸思考:合并指定的列
合并指定的列,需要前端指定合并的行,合并的列(这里默认从第一列开始合并),当传过来的数据当前行有 “//” 这个字符时,则表示当前行需要合并,该行最后一个 “//” 字符所在的位置,表示需要合并列的位置。
修改处理表格的类
**
* 定义处理表格的插件
* 与原方法的区别:
* // 增加表头行数位置
* private int rowIndex;
* public DetailTablePolicy(int rowIndex) {this.rowIndex = rowIndex;}
* // 通过传过来的数据决定插入的位置
* XWPFTableRow headerRow = table.getRow(rowIndex);
* // 行数据紧跟表头位置
* int index = rowIndex + 1;
*/
public class DetailTablePolicy extends DynamicTableRenderPolicy {
// 增加表头行数位置
private int rowIndex;
public DetailTablePolicy(int rowIndex) {
this.rowIndex = rowIndex;
}
@Override
public void render(XWPFTable table, Object object) throws Exception {
if (null == object) {
return;
}
try {
// 获取数据
LinkedHashMap<String, Object> mapValue = (LinkedHashMap<String, Object>) object;
// 添加表头数据,根据表头行位置来决定位置
XWPFTableRow headerRow = table.getRow(rowIndex);
int columnIndex = 0;
for (String columnName : mapValue.keySet()) {
XWPFTableCell headerCell = headerRow.getCell(columnIndex);
// 设置垂直居中
headerCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
XWPFParagraph paragraph = headerCell.getParagraphArray(0);
paragraph.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = paragraph.createRun();
// 设置大小,加粗和列名
run.setFontSize(11);
run.setBold(true);
run.setText(columnName);
columnIndex++;
}
// 获取列数
int size = 0;
for (Object value : mapValue.values()) {
if (value instanceof List) {
size = ((List) value).size();
break;
}
break;
}
// 先把列的数据转成行数据
List<List<String>> values = new ArrayList<>();
for (int i = 0; i < size; i++) {
List<String> item = new ArrayList<String>();
for (Object ob : mapValue.values()) {
if (ob instanceof List) {
item.add((String) ((List) ob).get(i));
}
}
values.add(item);
}
// 行数据按紧跟表头位置
int index = rowIndex + 1;
for (List<String> list : values) {
XWPFTableRow row = table.insertNewTableRow(index);
row.setHeight(27*20);
columnIndex = 0;
for (String cellValue : list) {
XWPFTableCell cell = row.createCell();
// 设置整个表格里的数据垂直居中对齐
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
CTTc ctTc = cell.getCTTc();
CTP ctp = ctTc.sizeOfPArray() == 0 ? ctTc.addNewP() : ctTc.getPArray(0);
XWPFParagraph paragraph = cell.getParagraph(ctp);
paragraph.setAlignment(ParagraphAlignment.CENTER);
cell.setText(cellValue);
columnIndex++;
}
index++;
}
// 存储需要合并的行和列的位置信息的列表
List<int[]> mergeInfoList = new ArrayList<>();
// 遍历数据列表并记录需要合并的行和列的位置信息
for (int i = 0; i < values.size(); i++) {
List<String> rowValues = values.get(i);
int endIndex = -1;
endIndex = rowValues.lastIndexOf("//");
if (endIndex != -1) {
mergeInfoList.add(new int[]{i+1, endIndex});
}
}
// 遍历需要合并的信息,合并表格
for (int[] mergeInfo : mergeInfoList) {
TableTools.mergeCellsHorizonal(table, mergeInfo[0], 0, mergeInfo[1]);
}
} catch (Exception e) {
throw new Exception("模板文件中存在多余的编码!");
}
}
}
备注:其它不需要修改。
- 参考