文章目錄
- 前言
- 一、設置word模板
- 普通字段
- 列表字段
- 復選框
- 二、代碼
- 1. 引入POM
- 2. 模板放入項目
- 3.代碼
- 實體類
- 工具類
- 三、測試
- 四、運行結果
- 五、注意事項
前言
最近有個Java填充Word模板的需求,包括文本,列表和復選框勾選,寫一個工具類,以此記錄。
提示:以下是本篇文章正文內容,下面案例可供參考
一、設置word模板
選擇文檔中要填充的地方點擊->選擇插入->文檔部件->域->域名(mergeFeild)->填寫變量名稱.
普通字段
填充完畢:
列表字段
操作和普通字段一樣,區別是需要在首行第一列插入列表開始域,首行最后一列插入結束域,中間正常字段。格式為:StartTable:<數組字段名>,EndTable:<數組字段名>
復選框
復選框找了好多種方法嘗試沒有成功,最后取巧,和普通字段一樣設置占位符,通過代碼邏輯處理.
二、代碼
1. 引入POM
poi/hutool/aspose-words/gson:
<!-- hutool工具類 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.14</version></dependency><!--word模板數據解析--><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.9.0-beta</version></dependency><!-- word/pdf操作 --><dependency><groupId>com.aspose</groupId><artifactId>aspose-words</artifactId><version>18.8</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.9</version></dependency>
2. 模板放入項目
這里是放在項目里,也可以放在云上存儲。
破解文件放入resouce,否則會有水印,aspose-words在maven倉庫中也沒有,需要下載后安裝在本地倉庫。
安裝命令
mvn install:install-file -Dfile=路徑/aspose-words-18.8.jar -DgroupId=com.aspose -DartifactId=aspose-words -Dversion=18.8 -Dpackaging=jar
引入破解文件
<License><Data><Products><Product>Aspose.Total for Java</Product><Product>Aspose.Words for Java</Product></Products><EditionType>Enterprise</EditionType><SubscriptionExpiry>20991231</SubscriptionExpiry><LicenseExpiry>20991231</LicenseExpiry><SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber></Data><Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature>
</License>
jar包和license下載地址
3.代碼
實體類
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Arrays;
import java.util.List;@Data
@AllArgsConstructor
@NoArgsConstructor
public class FillWordDTO implements Serializable {private String name;private String age;private String yuyan;private String yingyu;private String deyu;private String fayu;private String zhengshu;private String yiji;private String erji;private List<ExperienceList> experienceList;public static FillWordDTO create(){FillWordDTO fillWordDTO = new FillWordDTO();fillWordDTO.setName("小王");fillWordDTO.setAge("18");fillWordDTO.setYuyan("?");fillWordDTO.setYingyu("?");fillWordDTO.setDeyu("□");fillWordDTO.setFayu("?");fillWordDTO.setZhengshu("?");fillWordDTO.setYiji("?");fillWordDTO.setErji("□");fillWordDTO.setExperienceList(Arrays.asList(new ExperienceList("小王", "2020-01-01", "2020-01-01", "小王"),new ExperienceList("小王", "2020-01-01", "2020-01-01", "小王")));return fillWordDTO;}
}@Data
@AllArgsConstructor
@NoArgsConstructor
class ExperienceList {private String school;private String startTime;private String endTime;private String remark;
}
工具類
import com.aspose.words.*;
import com.aspose.words.net.System.Data.DataRow;
import com.aspose.words.net.System.Data.DataTable;import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class ContractUtil {private ContractUtil() {}/*** 調整bufferedimage大小* @param source BufferedImage 原始image* @param targetW int 目標寬* @param targetH int 目標高* @param flag boolean 是否同比例調整* @return BufferedImage 返回新image*/public static BufferedImage resizeBufferedImage(BufferedImage source, int targetW, int targetH, boolean flag) {int type = source.getType();BufferedImage target = null;double sx = (double) targetW / source.getWidth();double sy = (double) targetH / source.getHeight();if (flag && sx > sy) {sx = sy;targetW = (int) (sx * source.getWidth());} else if(flag && sx <= sy){sy = sx;targetH = (int) (sy * source.getHeight());}if (type == BufferedImage.TYPE_CUSTOM) { // handmadeColorModel cm = source.getColorModel();WritableRaster raster = cm.createCompatibleWritableRaster(targetW, targetH);boolean alphaPremultiplied = cm.isAlphaPremultiplied();target = new BufferedImage(cm, raster, alphaPremultiplied, null);} else {target = new BufferedImage(targetW, targetH, type);}Graphics2D g = target.createGraphics();g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));g.dispose();return target;}/*** 填充 word 模板(object數據格式)** @param modelWordByte word模版二進制文件* @param obj 要填充的數據* @return 組合數據之后的word二進制*/public static byte[] fillWordDataByDomain(byte[] modelWordByte, Object obj) {try {Class<?> aClass = obj.getClass();Field[] fields = aClass.getDeclaredFields();Map<String, Object> data = new HashMap<>(fields.length);for (Field field : fields) {PropertyDescriptor pd = new PropertyDescriptor(field.getName(), aClass);Method method = pd.getReadMethod();String key = field.getName();Object value = method.invoke(obj);if (value != null) {data.put(key, value);}}return fillWordDataByMap(modelWordByte, data);} catch (Exception e) {e.printStackTrace();return new byte[0];}}/*** 填充 word 模板(map數據格式)** @param file word二進制* @param data 要填充的數據* @return 組合數據之后的word二進制*/public static byte[] fillWordDataByMap(byte[] file, Map<String, Object> data) throws Exception {byte[] ret = null;if (data == null || data.isEmpty()) {return ret;}try (InputStream is = new ByteArrayInputStream(file);ByteArrayOutputStream out = new ByteArrayOutputStream()) {Document doc = new Document(is);DocumentBuilder builder = new DocumentBuilder(doc);Map<String, String> toData = new HashMap<>();for (Map.Entry<String, Object> entry : data.entrySet()) {String key = entry.getKey();Object value = entry.getValue();// 處理表格數據if (value instanceof List && !key.equals("checkboxOptions")) {DataTable dataTable = fillListData((List) value, key, builder);doc.getMailMerge().executeWithRegions(dataTable);}// 圖片插入else if (value instanceof BufferedImage) {builder.moveToMergeField(key);builder.insertImage((BufferedImage) value);}// 其他普通字段正常填充else {String valueStr = String.valueOf(value);if (value != null && !"null".equals(valueStr)) {toData.put(key, valueStr);}}}// 執行普通字段合并String[] fieldNames = new String[toData.size()];String[] values = new String[toData.size()];int i = 0;for (Map.Entry<String, String> entry : toData.entrySet()) {fieldNames[i] = entry.getKey();values[i] = entry.getValue();i++;}doc.getMailMerge().execute(fieldNames, values);doc.save(out, SaveOptions.createSaveOptions(SaveFormat.DOCX));ret = out.toByteArray();}return ret;}/*** 勾選段落中的復選框字段(適用于 Aspose.Words 18.8)*/private static void checkTheCheckbox(Paragraph paragraph) throws Exception {FieldCollection fields = paragraph.getRange().getFields();int count = fields.getCount();for (int i = 0; i < count; i++) {com.aspose.words.Field field = fields.get(i);if (field.getType() == FieldType.FIELD_FORM_CHECK_BOX) {// 設置字段結果為 "?" 表示勾選(或根據模板實際顯示字符調整)setCheckboxChecked(field, true);}}}private static void setCheckboxChecked(com.aspose.words.Field field, boolean checked) throws Exception {if (checked) {field.setResult("?"); // 根據模板中實際勾選狀態設置} else {field.setResult("□"); // 可選:取消勾選}}/*** 封裝 list 數據到 word 模板中(word表格)** @param list 數據* @param tableName 表格列表變量名稱* @return word表格數據DataTable*/private static DataTable fillListData(List<Object> list, String tableName, DocumentBuilder builder) throws Exception {//創建DataTable,并綁定字段DataTable dataTable = new DataTable(tableName);for (Object obj : list) {//創建DataRow,封裝該行數據DataRow dataRow = dataTable.newRow();Class<?> objClass = obj.getClass();Field[] fields = objClass.getDeclaredFields();for (int i = 0; i < fields.length; i++) {Field field = fields[i];dataTable.getColumns().add(fields[i].getName());PropertyDescriptor pd = new PropertyDescriptor(field.getName(), objClass);Method method = pd.getReadMethod();dataRow.set(i, method.invoke(obj));}dataTable.getRows().add(dataRow);}return dataTable;}// private static License license = null;/*** 加載 license* 由于 aspose是收費的,若沒有 license,則會出現水印。*/static {try {InputStream is = ContractUtil.class.getResourceAsStream("/License.xml");License license = new License();license.setLicense(is);} catch (Exception e) {throw new RuntimeException("自動加載aspose證書文件失敗!");}}}
三、測試
main方法測試,可以根據實際需求改為response輸出或者上傳到存儲服務器后返回鏈接地址。
public static void main(String[] args) throws IOException {FillWordDTO fillWordDTO = FillWordDTO.create();// 讀取模板文件byte[] modelByte = Files.readAllBytes(Paths.get("E:\\project\\spring-demo\\src\\main\\resources\\templates\\test.docx"));// 調用工具類,獲取填充數據后的文件byte[] resultByte = ContractUtil.fillWordDataByDomain(modelByte, fillWordDTO);// 處理該二進制文件,此處處理為輸出到桌面File resultFile = new File("C:\\Users\\Lenovo\\Desktop\\demo.docx");FileOutputStream fos = new FileOutputStream(resultFile);fos.write(resultByte);fos.close();}
四、運行結果
五、注意事項
在編輯word域代碼時,有時會有隱藏的代碼導致填充失敗
Found end of mail merge region 'experienceList' that does not match start of mail merge region 'jlList'.
出現以上錯誤或者想查看域代碼,按如下操作:
在word中依次點擊「文件→選項→高級」,在「顯示文檔內容」區域勾選「顯示域代碼而非域值」,找到報錯域代碼后刪除,重新添加域就解決了。