天天看點

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

1 模闆填充介紹

aspose對表格進行模闆填充 和xdocreport一樣 使用word郵件合并的MergeField域插入變量 差別就是文法差異

但是文法是aspose簡單 而且功能更強大更友善 而且通過xdocreport填充變量生成的複選框 單選框 隻能在Microsoft Office套件的word中打開才正常顯示 用WPS打開就看不到了 不能相容 用aspose就可以相容2種word工具 而且用WPS打開後 對複選框進行點選 可以切換選中狀态

而且對于圖檔的填充 aspose也支援變量方式 而xdocreport僅支援用圖檔放在模闆中 再用标簽去設定變量名 很不友善 而且需要循環填充圖檔時 xdocreport就更複雜了 誰用誰哭泣 而aspose就友善多了 僅需要設定被替換的字元 然後克隆行 找到需要替換的字元的行列坐标就可以進行圖檔的插入了

還有一種對表格循環填充的方法 是進行表格行克隆 再對應的往單元格塞資料 适用于帶圖檔的單元格行的循環填充

1.1 郵件合并的MergeField域 設定需要被替換的變量

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)
Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

可以在域這個選項上右鍵 添加到快捷通路工具欄 這樣以後就可以直接點選左上角的快捷按鈕進行操作了

而且建立了一個 其餘可以複制後編輯 點選後将光标放在變量中間 然後右鍵 可以進入編輯域

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

1.2 需要處理循環行中 帶有圖檔的單元格時 可以使用克隆行後進行變量坐标鎖定後插入圖檔

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

直接在word中填寫變量名稱 實作循環導出時就找到需要克隆的行 進行克隆後 對于文字進行替換變量字元 對于圖檔進行變量删除後插入圖檔

1.3 嵌套表格

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

對于這種一行中的單元格 還嵌套了一個表格 且需要循環導出時 也可以實作填充 圖檔顯示了填充的變量域的設定方法 代碼實作方法詳見下文

1.4 循環填充資料文法

在word填充中 通常在表格中含有子表 且子表的資料行是動态的 這時就需要用到循環的文法 來動态的擴充行

而且這個文法也适用于非表格的文字循環 也就是文字段落 需要循環填充的話 也可以使用 雖然文法的名字看起來像專門為表格設定的 但其實對于表格和文字段落的循環填充 都可以用

循環的文法 可以參見1.3中的嵌套表格 也是在MergeField的域中 設定變量 "TableStart:list" "TableEnd:list"

開始循環的變量 放在需要開始循環的位置前 以"TableStart"開頭 中間加":" 後面跟上變量名

結束循環的變量 放在需要結束循環的位置後 以"TableEnd"開頭 中間加":" 後面跟上變量名

用一對循環文法包裹需要循環的内容 即可以實作循環插入

2 填充的代碼實作

對于各種類型的填充 我已經封裝了方法 以便使用者僅需要關注填充的資料和模闆設定即可 無需再手動處理各種類型的填充邏輯

需要根據各種類型進行詳細研究的填充實作邏輯的 可以逐行拆解去了解下 還是有點複雜的

這裡簡單介紹下填充的入口方法fillTpl(LinkedHashMap<String, Object> dataMap, String resourceClassPath, Map<String,Integer>... tableIndexFieldMaps

其中

第1個參數dataMap 指的是需要填充的變量 以LinkedHashMap類型存入 為什麼我用了該類型 因為對于需要圖檔表格循環時 是需要定位最後一行進行克隆後複制的 以便周遊該map時 取到對應的map值也是最後才取到 確定最後取到的一行是真确的那行

  • 所有需要單個填充的變量 都存入該map 如果資料原本是對應 可以使用 BeanUtil.beanToMap(obj)方法進行轉換後加入到dataMap
  • 所有需要循環填充的list集合 也存到該map 但是對于不同填充方式需要注意存入dataMap值的list類型
  • --對于需要用占位符 也就是MergeField域來填充的 使用正常的ArrayList類型即可
  • --對于需要用到字元替換 也就是需要循環填充帶圖檔的表格行時 請使用LinkedList類型(這個類型的參數 僅限于最後一個表格行的循環填充 切記 具體原因上面已經講到過了 後續不再贅述)

第2個參數resourceClassPath 也就是模闆檔案在resources目錄中的位置 比如模闆在此 那就傳入"

template/complexTabel.docx"

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

第3個參數tableIndexFieldMaps 表格索引位和傳入dataMap的key的映射關系 可選參數 是專門用來指定dataMap中LinkedList類型的 因為克隆行時 需要指定表格的索引位 有時我們在填充模闆時 表格可能是有上下2個或多個組成的 是以需要指定這個映射關系

所有需要填充照片的 變量類型都使用byte[]

fillTpl方法的傳回類型為Pair key的值為word檔案的位元組數組 value的值為該word檔案的頁數

因為有時候會遇到更加複雜的需求 需要循環填充word 然後再将各個word合并成1個 還要在文檔頭部生成非标準的目錄 這時就需要用到每個文檔的頁數 以便手動統計各目錄對應的起始頁碼

對應單選 複選框的實作 是基于word中的字元來實作的 aspose填充時 需要設定字型和編碼

此處如果 有需要其他字元的 可以自己在方法中擴充

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)
Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

下面是封裝的工具類和方法的具體代碼

package com.example.support.tool;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.BiMap;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.aspose.words.*;
import com.aspose.words.net.System.Data.DataRelation;
import com.aspose.words.net.System.Data.DataRow;
import com.aspose.words.net.System.Data.DataSet;
import com.aspose.words.net.System.Data.DataTable;
import lombok.SneakyThrows;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.List;
import java.util.*;

/**
 * @author LWB
 * @Description aspose工具
 */
public class AsposeTool {

    /** 複選框 勾選 */
    public static final String BOX_CHECKED = "BOX_CHECKED";
    /** 複選框 未勾選 */
    public static final String BOX_UNCHECKED = "BOX_UNCHECKED";
    /** 單選框 勾選 */
    public static final String RADIO_CHECKED = "RADIO_CHECKED";
    /** 單選框 未勾選 */
    public static final String RADIO_UNCHECKED = "RADIO_UNCHECKED";
    /** 主表與子表的關聯ID:嵌套表時需要 */
    private static final String MAIN_SUB_TABLE_RELATION_KEY = "relationKey";


    /**
     * 擷取單元格的文本并移除"\f"分頁符
     * @param cell 單元格對象
     * @return
     */
    public static String getText(Cell cell){
        String text = cell.getText();
        return StrUtil.isBlank(text) ? "" : text.trim().replace("\f","");
    }

    /**
     * 擷取單元格種的圖檔 <br/>
     * 可能一個單元格 含有多個圖檔
     * @param cell 單元格對象
     * @return null-無圖檔
     */
    @SneakyThrows
    public static List<byte[]> getImg(Cell cell){
        NodeCollection shapeNodes = cell.getChildNodes(NodeType.SHAPE, true);
        if(shapeNodes.getCount() < 1) return null;
        List<byte[]> list = new ArrayList<>();
        for (Object childNode : shapeNodes) {
            Shape shape = (Shape)childNode;
            byte[] imageBytes = shape.getImageData().getImageBytes();
            list.add(imageBytes);
        }
        return list;
    }


    /**
     * 建立頁腳頁碼
     * @param doc
     * @param withTotalPage 是否顯示共 x 頁 預設 false
     */
    @SneakyThrows
    public static void addFooterPageNum(Document doc, boolean... withTotalPage){
        boolean hasTotalPage = false;
        if(ArrayUtil.isNotEmpty(withTotalPage)) hasTotalPage = withTotalPage[0];

        //建立頁腳 頁碼
        HeaderFooter footer = new HeaderFooter(doc, HeaderFooterType.FOOTER_PRIMARY);
        doc.getFirstSection().getHeadersFooters().add(footer);

        //頁腳段落
        Paragraph footerpara = new Paragraph(doc);
        footerpara.getParagraphFormat().setAlignment(ParagraphAlignment.CENTER);

        if(hasTotalPage){
            Run footerparaRun = new Run(doc,"共 ");
            footerparaRun.getFont().setName("宋體");
            footerparaRun.getFont().setSize(9.0);//小5号字型
            footerpara.appendChild(footerparaRun);

            footerpara.appendField(FieldType.FIELD_NUM_PAGES,true);//總頁碼
            footerparaRun = new Run(doc," 頁,第 ");
            footerparaRun.getFont().setName("宋體");
            footerparaRun.getFont().setSize(9.0);//小5号字型
            footerpara.appendChild(footerparaRun);

            footerpara.appendField(FieldType.FIELD_PAGE,true);//目前頁碼

            footerparaRun = new Run(doc," 頁");
            footerparaRun.getFont().setName("宋體");
            footerparaRun.getFont().setSize(9.0);//小5号字型
            footerpara.appendChild(footerparaRun);
        }else{
            Run footerparaRun = new Run(doc,"第 ");
            footerparaRun.getFont().setName("宋體");
            footerparaRun.getFont().setSize(9.0);//小5号字型
            footerpara.appendChild(footerparaRun);

            footerpara.appendField(FieldType.FIELD_PAGE,true);//目前頁碼

            footerparaRun = new Run(doc," 頁");
            footerparaRun.getFont().setName("宋體");
            footerparaRun.getFont().setSize(9.0);//小5号字型
            footerpara.appendChild(footerparaRun);
        }


        footer.appendChild(footerpara);
    }

    /**
     * 填充循環資料模闆(替換字元法) <br/>
     * 僅支援表格 填充内容類型:文字和圖檔都支援
     * @param list      資料集合
     * @param doc aspose.doc對象
     * @param tableIndex 表格的索引順序
     * @return word表格資料DataTable
     */
    @SneakyThrows
    public static void fillListData(List list, Document doc,int tableIndex) {
        DocumentBuilder builder = new DocumentBuilder(doc);
        Table table = (Table)doc.getChild(NodeType.TABLE, tableIndex, true);
        FindReplaceOptions findReplaceOptions = new FindReplaceOptions();
        findReplaceOptions.setFindWholeWordsOnly(true);
        RowCollection rows = table.getRows();
        int startRowIndex = rows.getCount() - 1;
        //先複制行
        for (Object obj : list) {
            Node deepClone = table.getLastRow().deepClone(true);
            table.getRows().add(deepClone);
        }
        table.getLastRow().remove();//将多餘的1行删除

        //再填充資料(更新行的擷取)
        rows = table.getRows();
        //将第1個資料行的被替換字段與行索引形成map映射
        CellCollection cells = rows.get(rows.getCount()-1).getCells();
        int columnCount = cells.getCount();
        BiMap<String,Integer> fieldNameIndexMap = new BiMap<>(new HashMap<>());
        for (int colIndex = 0; colIndex < columnCount; colIndex++) {
            String text = AsposeTool.getText(cells.get(colIndex));
            if(StrUtil.isNotBlank(text)) text = text.trim();
            fieldNameIndexMap.put(text,colIndex);
        }

        //表格的第一個資料行索引 = 标題行的數量
        int curRowIndex = startRowIndex;
        for (int i = 0; i < list.size(); i++) {
            Range range = rows.get(curRowIndex).getRange();

            Object obj = list.get(i);
            Map<String, Object> beanToMap = BeanUtil.beanToMap(obj);
            Set<String> keySet = beanToMap.keySet();
            for (String key : keySet) {
                Object val = beanToMap.get(key);
                String valStr = "";
                if(null != val && ClassUtil.isSimpleValueType(val.getClass())) valStr = val.toString();
                //先将被替換字段用空字元代替,再插入圖檔
                if(val instanceof byte[]){
                    range.replace(key,"",findReplaceOptions);
                    builder.moveToCell(tableIndex,curRowIndex,fieldNameIndexMap.get(key),0);
                    builder.insertImage((byte[])val);
                }else{
                    range.replace(key, valStr, findReplaceOptions);
                }
            }

            curRowIndex++;
        }

    }

    /**
     * 填充循環資料模闆(占位符法) <br/>
     * 表格和段落都支援 填充内容僅支援文字
     * @param list
     * @param tableName
     * @return
     */
    public static DataTable fillListData(List list, String tableName) {
        //建立DataTable
        DataTable dataTable = new DataTable(tableName);
        //綁定字段
        if(CollUtil.isNotEmpty(list)){
            Object obj = list.get(0);
            Map<String, Object> beanToMap = BeanUtil.beanToMap(obj);
            Set<String> keySet = beanToMap.keySet();
            for (String key : keySet) {
                dataTable.getColumns().add(key);
            }
        }
        //填充資料
        for (Object obj : list){
            //建立DataRow,封裝該行資料
            DataRow dataRow = dataTable.newRow();
            Map<String, Object> beanToMap = BeanUtil.beanToMap(obj);
            Set<String> keySet = beanToMap.keySet();
            for (String key : keySet) {
                Object value = beanToMap.get(key);
                dataRow.set(key,value);
            }
            dataTable.getRows().add(dataRow);
        }

        return dataTable;
    }

    /**
     * 填充嵌套循環模闆(占位符法) <br/>
     * @param dataList
     * @param tableName
     * @return
     */
    public static DataSet fillNestedListData(List dataList, String tableName) {
        DataSet dataSet = new DataSet();
        //建立DataTable
        DataTable mainTable = new DataTable(tableName);
        dataSet.getTables().add(mainTable);
        Map<String,DataTable> subTableMap = new HashMap<>();
        //綁定主表字段
        if(CollUtil.isNotEmpty(dataList)){
            //設定主表與子表的關聯ID字段
            mainTable.getColumns().add(MAIN_SUB_TABLE_RELATION_KEY);

            Object obj = dataList.get(0);
            Map<String, Object> beanToMap = BeanUtil.beanToMap(obj);
            Set<String> keySet = beanToMap.keySet();
            for (String key : keySet) {
                Object value = beanToMap.get(key);
                if(value instanceof List){ //集合 建立子表
                    List subList = (List) value;
                    //子表
                    DataTable subTable = new DataTable(key);
                    dataSet.getTables().add(subTable);

                    //設定主表與子表的關聯ID字段
                    subTable.getColumns().add(MAIN_SUB_TABLE_RELATION_KEY);

                    //綁定子表字段
                    if(CollUtil.isNotEmpty(subList)){
                        Object subObj = subList.get(0);
                        Map<String, Object> subMap = BeanUtil.beanToMap(subObj);
                        Set<String> subKeySet = subMap.keySet();
                        for (String subKey : subKeySet) {
                            subTable.getColumns().add(subKey);
                        }
                        subTableMap.put(key,subTable);
                    }
                }else{//非集合 直接添加字段
                    mainTable.getColumns().add(key);
                }

            }
        }

        //填充資料
        int relationKey = 0;
        for (Object obj : dataList){
            //建立DataRow,封裝該行資料
            DataRow dataRow = mainTable.newRow();
            //給關聯ID指派
            dataRow.set(MAIN_SUB_TABLE_RELATION_KEY,relationKey);

            Map<String, Object> beanToMap = BeanUtil.beanToMap(obj);
            Set<String> keySet = beanToMap.keySet();
            for (String key : keySet) {
                Object value = beanToMap.get(key);
                if(value instanceof List){
                    List subList = (List) value;
                    //填充資料
                    for (Object subObj : subList){
                        //建立DataRow,封裝該行資料
                        DataTable subTable = subTableMap.get(key);
                        DataRow subDataRow = subTable.newRow();
                        //給關聯ID指派
                        subDataRow.set(MAIN_SUB_TABLE_RELATION_KEY,relationKey);
                        Map<String, Object> subMap = BeanUtil.beanToMap(subObj);
                        Set<String> subKeySet = subMap.keySet();
                        for (String subKey : subKeySet) {
                            Object subValue = subMap.get(subKey);
                            subDataRow.set(subKey,subValue);
                        }
                        subTable.getRows().add(subDataRow);
                    }
                }else{
                    dataRow.set(key,value);
                }
            }

            mainTable.getRows().add(dataRow);
            relationKey++;
        }

        //建立主表子表的關聯
        subTableMap.forEach((subTableName,subTable) -> dataSet.getRelations().add(new DataRelation(RandomUtil.randomString(8), mainTable.getColumns().get(MAIN_SUB_TABLE_RELATION_KEY), subTable.getColumns().get(MAIN_SUB_TABLE_RELATION_KEY))));

        return dataSet;
    }

    /**
     * 填充模闆(替換字元法) <br/>
     * 注意: <br/>
     * 需要程式逐行複制表格來生成資料的(替換字元方法),dataMap的value類型請使用LinkedList(僅支援所在表格的最後一行的情況) <br/>
     * 需要用占位符來循環生成表格資料的,dataMap的value類型請使用List
     * @param dataMap 填充的資料
     * @param resourceClassPath 模闆在resources中的位址 like "template/asposeTpl.docx"
     * @param tableIndexFieldMaps word中的表格 需要複制行來完成表格填充時 即用到LinkedList dataMap資料的字段key 和表格的索引位置 map
     * @return
     */
    @SneakyThrows
    public static Pair<byte[],Integer> fillTpl(LinkedHashMap<String, Object> dataMap, String resourceClassPath, Map<String,Integer>... tableIndexFieldMaps){
        Map<String,Integer> tableIndexFieldMap = new HashMap<>();
        if(ArrayUtil.isNotEmpty(tableIndexFieldMaps)) tableIndexFieldMap = Arrays.stream(tableIndexFieldMaps).findFirst().get();
        //擷取填充模闆
        Resource resource = new ClassPathResource(resourceClassPath);
        try(
            InputStream tplIns = resource.getStream();
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ){
            Document doc = new Document(tplIns);
            DocumentBuilder builder = new DocumentBuilder(doc);

            String[] keyArr = dataMap.keySet().stream().toArray(String[]::new);
            Map<String, Object> textMap = new HashMap();
            for (String key : keyArr) {
                Object value = dataMap.getOrDefault(key, null);
                if(value instanceof LinkedList){//LinkedList 複制行生成表格資料專用類型
                    int tableIndex = tableIndexFieldMap.getOrDefault(key, -1);
                    Assert.isTrue(tableIndex > -1,"請在tableIndexFieldMap中傳入對應表格資料字段{}在word種的表格索引順序",key);
                    AsposeTool.fillListData((List) value,doc,tableIndex);
                }else if(value instanceof List){//使用DataSet來動态生成循環數 可嵌套循環資料
                    DataSet dataSet = AsposeTool.fillNestedListData((List) value, key);
                    doc.getMailMerge().executeWithRegions(dataSet);
                }else if(value instanceof byte[]){
                    builder.moveToMergeField(key);
                    builder.insertImage((byte[]) value);
                }else if(value instanceof String){
                    String valStr = (String) value;
                    if(AsposeTool.BOX_CHECKED.equals(valStr)){
                        builder.moveToMergeField(key);
                        //設定字型
                        builder.getFont().setName("Wingdings 2");
                        builder.write("\uF052");
                    }else if(AsposeTool.BOX_UNCHECKED.equals(valStr)){
                        builder.moveToMergeField(key);
                        //設定字型
                        builder.getFont().setName("Wingdings 2");
                        builder.write("\uF0A3");
                    }else if(AsposeTool.RADIO_CHECKED.equals(valStr)){
                        builder.moveToMergeField(key);
                        //設定字型
                        builder.getFont().setName("Wingdings 2");
                        builder.write("\uF09B");
                    }else if(AsposeTool.RADIO_UNCHECKED.equals(valStr)){
                        builder.moveToMergeField(key);
                        //設定字型
                        builder.getFont().setName("Wingdings 2");
                        builder.write("\uF099");
                    }else{
                        textMap.put(key,valStr);
                    }
                }else{
                    textMap.put(key,value);
                }
            }
            String[] textKeyArr = textMap.keySet().stream().toArray(String[]::new);
            List<Object> textValList = new ArrayList();
            for (String key : textKeyArr) {
                textValList.add(textMap.get(key));
            }
            Object[] textValArr = textValList.stream().toArray(Object[]::new);
            doc.getMailMerge().execute(textKeyArr,textValArr);

            int saveFormat = "docx".equalsIgnoreCase(FileUtil.extName(resourceClassPath)) ? SaveFormat.DOCX : SaveFormat.DOC;
            doc.save(bout, saveFormat);
            int pageCount = doc.getPageCount();
            return new Pair(bout.toByteArray(),pageCount);
        }
    }


}

           

3 示例

本示例中 展示了各種填充類型:單個變量的替換 照片 單選框 複選框 表格中文字行循環 嵌套表格循環 帶圖檔和文字的表格行循環

3.1 需要填充的模闆

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

3.2 實作方法

/**
     * 根據模闆導出word
     * @return
     */
    @SneakyThrows
    @PostMapping("fillTemplate")
    public Ret fillTemnlate(){
        byte[] photoBytes = null;
        byte[] netBytes = null;
        byte[] fireBytes = null;
        try(
            ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
            ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
            ByteArrayOutputStream bos3 = new ByteArrayOutputStream();
        ){
            Thumbnails.of("C:/Users/Administrator/Desktop/aspose/紅孩兒.jpg").forceSize(60,100).toOutputStream(bos1);
            photoBytes = bos1.toByteArray();
            Thumbnails.of("C:/Users/Administrator/Desktop/aspose/蜘蛛網.jpg").forceSize(100,100).toOutputStream(bos2);
            netBytes = bos2.toByteArray();
            Thumbnails.of("C:/Users/Administrator/Desktop/aspose/噴火.jpg").forceSize(100,100).toOutputStream(bos3);
            fireBytes = bos3.toByteArray();
        }
        //準備導出的資料
        PersonInfo personInfo = new PersonInfo().setName("紅孩兒").setIdNo("123").setTel("666").setAddr("火焰山").setTopEdu("國小")
                .setRadioMale(AsposeTool.RADIO_CHECKED).setRadioFemale(AsposeTool.RADIO_UNCHECKED)
                .setCheckboxSwim(AsposeTool.BOX_CHECKED).setCheckboxRun(AsposeTool.BOX_CHECKED).setCheckboxJump(AsposeTool.BOX_UNCHECKED)
                .setPersonPhoto(photoBytes);
        ArrayList<EduExpe> eduList = new ArrayList<>();
        eduList.add(new EduExpe().setStartTime("2020年9月").setEndTime("2021年7月").setSchool("盤絲洞幼稚園").setMajor("織網"));
        eduList.add(new EduExpe().setStartTime("2021年9月").setEndTime("2022年7月").setSchool("火焰山國小").setMajor("噴火"));
        ArrayList<HisScore> hisScoreList = new ArrayList<>();
        ArrayList<SubjectScore> subjectScoreList_A = new ArrayList<>();
        subjectScoreList_A.add(new SubjectScore().setYear("2020").setScore("80"));
        subjectScoreList_A.add(new SubjectScore().setYear("2021").setScore("90"));
        ArrayList<SubjectScore> subjectScoreList_B = new ArrayList<>();
        subjectScoreList_B.add(new SubjectScore().setYear("2021").setScore("85"));
        subjectScoreList_B.add(new SubjectScore().setYear("2022").setScore("95"));
        hisScoreList.add(new HisScore().setSubject("吐絲").setSubjectScoreList(subjectScoreList_A));
        hisScoreList.add(new HisScore().setSubject("點火").setSubjectScoreList(subjectScoreList_B));
        LinkedList<PersonPaper> paperList = new LinkedList<>();
        paperList.add(new PersonPaper().setGainTime("2021年").setPaperName("織網小能手").setPaperPhoto(netBytes));
        paperList.add(new PersonPaper().setGainTime("2022年").setPaperName("噴火大王").setPaperPhoto(fireBytes));
        LinkedHashMap<String,Object> dataMap = new LinkedHashMap<>();
        Map<String, Object> personInfoMap = BeanUtil.beanToMap(personInfo);
        dataMap.putAll(personInfoMap);
        dataMap.put("eduList",eduList);
        dataMap.put("paperList",paperList);
        dataMap.put("hisScoreList",hisScoreList);
        //對于使用複制行 來完成資料疊代插入的情況 必須确定需要複制的行在表格的最後一行
        Map<String,Integer> cloneKeyTableIndexMap = new HashMap<>();
        cloneKeyTableIndexMap.put("paperList",0);

        //導入
        Pair<byte[], Integer> resultPair = AsposeTool.fillTpl(dataMap, "templates/complexTable.docx",cloneKeyTableIndexMap);
        byte[] bytes = resultPair.getKey();
        //接口直接傳回檔案的話 直接使用bytes就行 方法傳回類型使用ResponseEntity<byte[]>
        //以下是儲存檔案的方法
        InputStream inputStream = new ByteArrayInputStream(bytes);
        Document doc = new Document(inputStream);
        doc.save("C:/Users/Administrator/Desktop/aspose/模闆填充.docx", SaveFormat.DOCX);
        return Ret.success();
    }           

可以看到 封裝後 實作導入起始很簡單了 隻需要按照入參規則準備好需要導入datamap 一步就可以實作填充

3.3 實作效果

Java word aspose(3.14 模闆導出word 循環圖檔 循環嵌套表格 複選單選)

基本上到此 一般情況下 各種填充的需求都可以滿足了

還有特别變态的需求 要多個文檔合并 加目錄 加頁碼 或者非标準目錄(手動生成目錄的) 請自己結合各種提到的方法去實作吧 有疑問可以留言讨論

aspose正常使用 就到此結束了

當然他還包含了各種生成word方法的封裝 一般也不怎麼常用 就不再讨論了有實際需求的 可以自行了解下

以下是官方demo 包含了各種例子 挺全的

GitHub - aspose-words/Aspose.Words-for-Java: Aspose.Words for Java examples, plugins and showcases​

以下是API文檔

Document | Aspose.Words for Java​

不能直接看源碼 源碼都是被加密混淆過的 但是基于以上2個文檔 基本就可以整明白所需的功能了