接上篇
注:Consts 类放到了本篇文章最下面
上篇的文章地址(也可直接点我主页去看):https://blog.csdn.net/C__jx/article/details/127110219?spm=1001.2014.3001.5501
一、优化点:
优化过后
1、纯文本模版也支持图片插入
2、 兼容部分
a
a
,
{aa},
aa,{3, }问题
3、可自由设置图片宽高
直接上代码了,代码里面有注释,如有问题,希望指出,感谢指导
package com.zjjw.jxtest.util.util;
import com.zjjw.platform.core.consts.Consts;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
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;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StringUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* 生成word 工具类
*
* @author: chenjiaxiang
* @create: 2022/9/29 11:27
**/
@Slf4j
public class CreateWordPoiUtils {
private static final String FILE = "file";
private static final String FILE_F = "File";
private static final String BASE64 = "base64";
private static final String BASE64_B = "Base64";
private static final String JSON_OBJECT_START_$ = "${";
/**
* 根据docx模版生成docx文件 适用于做测试用的 main
*
* @param templateName 模版路径名称
* @param insertTextMap ${} 字符替换
* @param map 表格数据需循环新增 动态数据
* @param filePath 输出到本地的文件路径
* @param width 插入图片的宽度 不传默认为128
* @param high 插入图片的高度 不传默认为128
*/
public static void generateWord(String templateName, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map, String filePath, Integer width, Integer high) {
getMain(templateName, insertTextMap, map, filePath, width, high);
}
/**
* 根据docx模版生成docx文件 适用于做测试用的 main
*
* @param templateName 模版路径名称
* @param insertTextMap ${} 字符替换
* @param map 表格数据需循环新增 动态数据
* @param filePath 输出到本地的文件路径
*/
public static void generateWord(String templateName, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map, String filePath) {
getMain(templateName, insertTextMap, map, filePath, 128, 128);
}
/**
* 根据docx模版生成docx文件
*
* @param templateName 模版路径名称
* @param insertTextMap ${} 字符替换
* @param map 表格数据需循环新增 动态数据
* @param width 插入图片的宽度 不传默认为128
* @param high 插入图片的高度 不传默认为128
* @return InputStream流
*/
public static InputStream generateWord(String templateName, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map, Integer width, Integer high) {
return getInputStream(templateName, insertTextMap, map, width, high);
}
/**
* 根据docx模版生成docx文件
*
* @param templateName 模版路径名称
* @param insertTextMap ${} 字符替换
* @param map 表格数据需循环新增 动态数据
* @return InputStream流
*/
public static InputStream generateWord(String templateName, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map) {
return getInputStream(templateName, insertTextMap, map, 128, 128);
}
/**
* 根据docx模版生成docx文件 适用于做测试用的 main
*
* @param templateName 模版路径名称
* @param insertTextMap ${} 字符替换
* @param map 表格数据需循环新增 动态数据
* @param filePath 输出到本地的文件路径
* @param width 插入图片的宽度 不传默认为128
* @param high 插入图片的高度 不传默认为128
*/
private static void getMain(String templateName, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map, String filePath, Integer width, Integer high) {
InputStream inputStream = null;
//获取docx解析对象
XWPFDocument xwpfDocument = null;
FileOutputStream fileOutputStream = null;
try {
String fileLocal = String.format("%s%s.docx", filePath, UUID.randomUUID());
// 设置模板输入和结果输出
fileOutputStream = new FileOutputStream(fileLocal);
inputStream = Files.newInputStream(new File(templateName).toPath());
//获取docx解析对象
xwpfDocument = new XWPFDocument(inputStream);
// 处理所有文段数据,除了表格
handleParagraphs(xwpfDocument, insertTextMap, width, high);
// 处理表格数据
handleTable(xwpfDocument, insertTextMap, map, width, high);
//文档写入流
xwpfDocument.write(fileOutputStream);
log.info("word生成成功");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (xwpfDocument != null) {
xwpfDocument.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 根据docx模版生成docx文件
*
* @param templateName 模版路径名称
* @param insertTextMap ${} 字符替换
* @param map 表格数据需循环新增 动态数据
* @param width 插入图片的宽度 不传默认为128
* @param high 插入图片的高度 不传默认为128
*/
private static InputStream getInputStream(String templateName, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map, Integer width, Integer high) {
ClassPathResource classPathResource = new ClassPathResource(templateName);
InputStream inputStream = null;
//获取docx解析对象
XWPFDocument xwpfDocument = null;
ByteArrayOutputStream baos = null;
try {
inputStream = classPathResource.getInputStream();
//获取docx解析对象
xwpfDocument = new XWPFDocument(inputStream);
// 处理所有文段数据,除了表格
handleParagraphs(xwpfDocument, insertTextMap, width, high);
// 处理表格数据
handleTable(xwpfDocument, insertTextMap, map, width, high);
//二进制OutputStream
baos = new ByteArrayOutputStream();
//文档写入流
xwpfDocument.write(baos);
log.info("word生成成功");
//OutputStream写入InputStream二进制流
return new ByteArrayInputStream(baos.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (xwpfDocument != null) {
xwpfDocument.close();
}
if (baos != null) {
baos.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 处理需要替换的数据和 需要表格新增的数据
*
* @param xwpfDocument docx
* @param insertTextMap ${} 数据
* @param map 表格数据
* @param width 图片的宽度
* @param high 图片的高度
*/
public static void handleTable(XWPFDocument xwpfDocument, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map, Integer width, Integer high) {
List<XWPFTable> tables = xwpfDocument.getTables();
int tableIndex = 0;
for (XWPFTable table : tables) {
//表格坐标
tableIndex++;
List<XWPFTableRow> rows = table.getRows();
if (rows.size() > 0) {
if (isReplacement(table.getText())) {
// 替换数据
rows.stream().map(XWPFTableRow::getTableCells).forEach(tableCells -> {
tableCells.stream().filter(tableCell -> isReplacement(tableCell.getText())).forEach(tableCell -> {
List<XWPFParagraph> paragraphs = tableCell.getParagraphs();
//替换数据
paragraphs.stream().map(XWPFParagraph::getRuns).forEach(runs -> {
//获取map的key
runs.forEach(run -> {
String key = matchesKey(run.text(), insertTextMap);
if (Objects.isNull(insertTextMap.get(key))) {
return;
}
String string = insertTextMap.get(key).toString().replace(JSON_OBJECT_START_$, Consts.EMPTY_STRING).replace(Consts.JSON_OBJECT_END, Consts.EMPTY_STRING);
//表格替换
tableReplace(key, insertTextMap, run, string, width, high);
});
});
});
});
} else {
//处理新增表格数据
if (Objects.nonNull(map.get(tableIndex))) {
List<String[]> strings = map.get(tableIndex);
// 插入数据
IntStream.range(1, strings.size()).forEach(i -> table.createRow());
List<XWPFTableRow> rowList = table.getRows();
for (int i = 1; i < rowList.size(); i++) {
XWPFTableRow xwpfTableRow = rowList.get(i);
List<XWPFTableCell> tableCells = xwpfTableRow.getTableCells();
for (int j = 0; j < tableCells.size(); j++) {
XWPFTableCell xwpfTableCell = tableCells.get(j);
xwpfTableCell.setText(strings.get(i - 1)[j]);
}
}
}
}
}
}
}
/**
* 替换带有 ${}的数据
*
* @param wordValue 表格文本
* @param map 数据
* @param run 文档数据
* @param width 图片的宽度
* @param high 图片的高度
*/
public static void matchesValue(String wordValue, Map<String, Object> map, XWPFRun run, Integer width, Integer high) {
if (JSON_OBJECT_START_$.equals(wordValue) || Consts.JSON_OBJECT_END.equals(wordValue) || Consts.JSON_OBJECT_START.equals(wordValue)) {
setNull(run);
}
for (String key : map.keySet()) {
String s1 = JSON_OBJECT_START_$ + key + Consts.JSON_OBJECT_END;
String s2 = JSON_OBJECT_START_$ + key;
String s3 = key + Consts.JSON_OBJECT_END;
String string = map.get(key).toString().replace(JSON_OBJECT_START_$, Consts.EMPTY_STRING).replace(Consts.JSON_OBJECT_END, Consts.EMPTY_STRING);
if (s1.equals(wordValue) || key.equals(wordValue) || s2.equals(wordValue) || s3.equals(wordValue)) {
//表格替换
tableReplace(key, map, run, string, width, high);
}
}
}
/**
* 表格、纯文本的数据转换
*
* @param key map的key
* @param map map数据
* @param run 文件行
* @param file 图片的url或者base64字符传,多个以,分割
* @param width 图片的宽度
* @param high 图片的高度
*/
private static void tableReplace(String key, Map<String, Object> map, XWPFRun run, String file, Integer width, Integer high) {
try {
if (key.contains(FILE) || key.contains(FILE_F)) {
setNull(run);
for (String ss : file.split(Consts.DEFAULT_SEPARATOR)) {
//url转input
//参数1:图片流数据 参数2:图片类型 参数3 图片名称 参数4:图片宽度 参数5:图片高度
InputStream inputStream = getInputStream(ss);
run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, UUID.randomUUID().toString(), Units.toEMU(width), Units.toEMU(high));
inputStream.close();
}
} else if (key.contains(BASE64) || key.contains(BASE64_B)) {
setNull(run);
for (String ss : file.split(Consts.DEFAULT_SEPARATOR)) {
//base64转input
//参数1:图片流数据 参数2:图片类型 参数3 图片名称 参数4:图片宽度 参数5:图片高度
InputStream inputStream = base64Input(ss);
run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, UUID.randomUUID().toString(), Units.toEMU(width), Units.toEMU(high));
inputStream.close();
}
} else if (Consts.JSON_OBJECT_END.equals(key)) {
setNull(run);
} else {
run.setText(String.valueOf(map.get(key)), 0);
}
} catch (InvalidFormatException | IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获取map的key
*/
public static String matchesKey(String wordValue, Map<String, Object> map) {
if (JSON_OBJECT_START_$.equals(wordValue) || Consts.JSON_OBJECT_END.equals(wordValue) || Consts.JSON_OBJECT_START.equals(wordValue)) {
wordValue = "";
return wordValue;
}
for (String s : map.keySet()) {
String s1 = JSON_OBJECT_START_$ + s + Consts.JSON_OBJECT_END;
String s2 = JSON_OBJECT_START_$ + s;
String s3 = s + Consts.JSON_OBJECT_END;
if (s1.equals(wordValue) || s.equals(wordValue) || s2.equals(wordValue) || s3.equals(wordValue)) {
wordValue = s;
}
}
return wordValue;
}
/**
* 判断是否有包含需替换的数据
*
* @param text 文本
* @return boolean
*/
public static boolean isReplacement(String text) {
boolean check = false;
if (text.contains(Consts.DOLLAR)) {
check = true;
}
return check;
}
/**
* 处理文段数据
*
* @param xwpfDocument docx
* @param insertTextMap ${}数据
*/
public static void handleParagraphs(XWPFDocument xwpfDocument, Map<String, Object> insertTextMap, Integer width, Integer high) {
xwpfDocument.getParagraphs().forEach(paragraph -> {
String text = paragraph.getText();
if (isReplacement(text)) {
for (XWPFRun run : paragraph.getRuns()) {
// 判断带有${}的run
// run.setText(matchesValue(run.text(), insertTextMap), 0);
matchesValue(run.text(), insertTextMap, run, width, high);
}
}
});
}
/**
* base64转为输入流
*
* @param base64 编码
* @return 输入流
*/
public static InputStream base64Input(String base64) {
BASE64Decoder decoder = new BASE64Decoder();
InputStream inputStream;
// Base64解码
byte[] byteArr = new byte[0];
try {
byteArr = decoder.decodeBuffer(base64);
inputStream = new ByteArrayInputStream(byteArr);
} catch (IOException e) {
throw new RuntimeException(e);
}
return inputStream;
}
/**
* 【获取网络文件的输入流】
*
* @param filePath: 网络文件路径
* @return java.io.InputStream
*/
public static InputStream getInputStream(String filePath) throws IOException {
InputStream inputStream = null;
//创建URL
URL url = new URL(filePath);
//试图连接并取得返回状态码
URLConnection urlconn = url.openConnection();
urlconn.connect();
HttpURLConnection httpconn = (HttpURLConnection) urlconn;
int httpResult = httpconn.getResponseCode();
if (httpResult == HttpURLConnection.HTTP_OK) {
inputStream = urlconn.getInputStream();
}
return inputStream;
}
/**
* 图片URL转Base64编码
*
* @param imgUrl 图片URL
* @return Base64编码
*/
public static String imageUrlToBase64(String imgUrl) {
URL url = null;
InputStream is = null;
ByteArrayOutputStream outStream = null;
HttpURLConnection httpUrl = null;
try {
url = new URL(imgUrl);
httpUrl = (HttpURLConnection) url.openConnection();
httpUrl.connect();
httpUrl.getInputStream();
is = httpUrl.getInputStream();
outStream = new ByteArrayOutputStream();
//创建一个Buffer字符串
byte[] buffer = new byte[1024];
//每次读取的字符串长度,如果为-1,代表全部读取完毕
int len = 0;
//使用输入流从buffer里把数据读取出来
while ((len = is.read(buffer)) != -1) {
//用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度
outStream.write(buffer, 0, len);
}
// 对字节数组Base64编码
return encode(outStream.toByteArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (outStream != null) {
outStream.close();
}
if (httpUrl != null) {
httpUrl.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* 图片转字符串
*
* @param image 图片Buffer
* @return Base64编码
*/
public static String encode(byte[] image) {
BASE64Encoder decoder = new BASE64Encoder();
return replaceEnter(decoder.encode(image));
}
/**
* 字符替换
*
* @param str 字符串
* @return 替换后的字符串
*/
public static String replaceEnter(String str) {
String reg = "[\n-\r]";
Pattern p = Pattern.compile(reg);
Matcher m = p.matcher(str);
return m.replaceAll("");
}
/**
* 将对象集合转为一个String[] 集合
*
* @param tList 对象集合
* @param <T> 参数
* @return 数组集合
*/
public static <T> List<String[]> listToArray(List<T> tList) {
ArrayList<String[]> arrayList = new ArrayList<>();
tList.forEach(t -> {
String[] filedName = getFiledName(t);
List<String> to = Arrays.stream(filedName).map(s -> getFieldValueByName(s, t))
.map(fieldValueByName -> StringUtils.isEmpty(fieldValueByName) ? "" : String.valueOf(fieldValueByName)).collect(Collectors.toList());
arrayList.add(to.toArray(new String[]{}));
});
return arrayList;
}
/**
* 获取对象的 属性名
*
* @param o 对象名称
* @return 属性数组
*/
private static String[] getFiledName(Object o) {
Field[] fields = o.getClass().getDeclaredFields();
return Arrays.stream(fields).map(Field::getName).toArray(String[]::new);
}
/**
* 根据字段名 获取该字段值
*
* @param fieldName 字段名
* @param o 对象
* @return 值
*/
private static Object getFieldValueByName(String fieldName, Object o) {
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = o.getClass().getMethod(getter);
return method.invoke(o);
} catch (Exception e) {
log.error("获取属性值失败!" + e, e);
}
return null;
}
/**
* setNull
*/
private static void setNull(XWPFRun run) {
run.setText(Consts.EMPTY_STRING, 0);
}
}
Consts类
public class Consts {
public static final int ZERO = 0;
public static final String[] BREAKERS = new String[]{"\r\n", "\n\r", "\n"};
public static final String[] BREAKERS_REPLACEMENT = new String[]{"\\\\r\\\\n", "\\\\n\\\\r", "\\\\n"};
public static final String DEFAULT_FILE_SEPARATOR = "/";
public static final String DOUBLE_FILE_SEPARATOR = "//";
public static final String HTTP_PROTOCOL = "http";
public static final String HTTPS_PROTOCOL = "https";
public static final String EMPTY_STRING = "";
public static final String DEFAULT_SEPARATOR = ",";
public static final String DOT = ".";
public static final String DOLLAR = "$";
public static final String STRING_FORMAT_PLACEHOLDER = "%s";
public static final String SEPARATOR_AND = "&";
public static final String EQUAL = "=";
public static final String QUESTION = "?";
public static final String PATTERN_ALL = "*";
public static final String ADD_OP = "+";
public static final String COLON = ":";
public static final String SEMICOLON_CHAR = ";";
public static final String JSON_ARRAY_START = "[";
public static final String JSON_ARRAY_END = "]";
public static final String JSON_OBJECT_START = "{";
public static final String JSON_OBJECT_END = "}";
public static final String APP_NAME = "app.name";
public static final String APPLICATION_NAME = "spring.application.name";
public static final String RUN_MODE = "run.mode";
public static final String CONFIG_ADDRESS = "config.address";
public static final String DISCOVERY_ADDRESS = "discovery.address";
public static final String CD_NAMESPACE = "config_discovery.namespace";
public static final String SERVICE_SIDE = "service.side";
public static final String SPRING_PROFILE = "spring.profiles.active";
public static final String SERVICE_PUBLIC = "public";
public static final String SERVICE_PRIVATE = "private";
public static final String SENTINEL_DASHBOARD = "sentinel.dashboard";
public static final String SENTINEL_PORT = "sentinel.port";
public static final String ACCESS_TOKEN = "accessToken";
public static final String DEVICE_NUMBER = "deviceNumber";
public static final String USER_CODE = "userCode";
public static final String APP_KEY = "appKey";
public static final String PLATFORM = "platform";
public static final String TIMESTAMP = "timestamp";
public static final String SIGN = "sign";
public static final String NONCE = "nonce";
public static final String MIDDLE_LINE = "-";
public static final int DEFAULT_PAGE_INDEX = 0;
public static final int DEFAULT_PAGE_SIZE = 20;
public static final int DEFAULT_MAX_PAGE_SIZE = 200;
public static final int ONT_YEAR_SECONDS = 31536000;
public static final int DEFAULT_THRESHOLD = 900;
private Consts() {
}
}