优化三、java XWPFDocument 生成word doc、docx 功能优化(插入图片,循环数据新增)

时间:2025-03-11 07:05:29
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%", 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 // (matchesValue((), 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 */ 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); } }