JAVA Asponse.Word Office 操做神器,藉助 word 模板生成 word 文檔,並轉化爲 pdf,png 等多種格式的文件

一,因爲該 jar 包不是免費的, maven 倉庫通常不會有,須要咱們去官網下載並安裝到本地 maven 倉庫html

1,用地址   https://www-evget-com/product/564    下載 19.4  和 18.1 兩個版本 (不知道爲何這個地址博客園不容許粘貼,請你們將域名的 - 換成 . 後在訪問)java

2,安裝到本地 maven 倉庫,不會安裝的請移步  https://www.cnblogs.com/lovling/p/10122207.htmlgit

3,筆者安裝的命令以下可作參考,這裏兩個 JAR 包放到 E 盤spring

mvn install:install-file -DgroupId=com.aspose -DartifactId=words -Dversion=19.4.jdk17 -Dpackaging=jar -D file=E:\aspose-words-19.4-jdk17.jar數組

mvn install:install-file -DgroupId=com.aspose -DartifactId=words -Dversion=18.1.jdk16 -Dpackaging=jar -D file=E:\aspose-words-18.1-jdk16.jar網絡

 

 

 

二,建立一個 maven 項目,引入剛剛安裝的依賴 common-asponse,這樣命名是爲了以後將該項目封裝成公共工具庫框架

<dependency>
    <groupId>com.aspose</groupId>
    <artifactId>words</artifactId>
    <version>19.4.jdk17</version>
    <!--<version>18.1.jdk16</vers-ion>-->
</dependency>

1,在 resource 目錄下建立一個 export 目錄,準備如下幾個文件,一張圖片,一個證書,分別用 wps 和 office 建立的 word 文檔dom

 

2,咱們建立一個 域 郵件合併的模板,這是一個有實體類點屬性的,兩層 FOR 循環,帶圖片的模板maven

3,注意域的建立方式,Ctrl + F9 -> 右鍵點擊生成的花括號 -> 選擇編輯域 -> 選擇郵件合併 -> 寫內容 ide

注意若是用 wps 不要動 MERGEFIELD 這個單詞,空一格在後面寫, office 則沒有 該單詞)

 三,模板準備完成後,開始寫 JAVA 代碼,

1,首先該框架 是不支持 Map 類型的數據的,因此咱們須要本身去實現接口  IMailMergeDataSource 具體看註解

package com.hwq.aspose.word;

import com.aspose.words.IMailMergeDataSource;

import java.util.List;
import java.util.ListIterator;
import java.util.Map;

public class MailMapData implements IMailMergeDataSource {

    private ListIterator<Map<String, Object>> list;
    private String name;
    private Map<String, Object> map;

    /**
     * 構造器初始化,將 list 集合轉化爲可迭代的對象,提升性能
     * @param name 健
     * @param list 值
     */
    public MailMapData(String name, List<Map<String, Object>> list) {
        this.name = name;
        this.list = list.listIterator();
    }

    /**
     * 獲取字段名的方法
     * @return 自定義的字段名
     */
    @Override
    public String getTableName() throws Exception {
        return name;
    }

    /**
     * 移動到下一組數據,並判斷下一組數據是否存在
     * @return 是否存在下一個數據
     */
    @Override
    public boolean moveNext() throws Exception {
        map = list.hasNext() ? list.next() : null;
        return map != null;
    }


    /**
     * 獲取當前字段的的數據,這裏注意 18.1 和 19.4 的版本有必定的區別,下面註釋的 19.4 版本的實現方式
     * 在循環一個 Map 的時候,取字段時會運行改方法
     * @param s   字段
     * @param ref 數據容器
     * @return
     * @throws Exception
     */
    /*
    @Override
    public boolean getValue(String key, Ref<Object> ref) throws Exception {
        Object value = map.get(key);
        ref.set(value);
        return value != null;
    }
    */
    @Override
    public boolean getValue(String key, Object[] args) throws Exception {
        Object value = map.get(key);
        args[0] = value;
        return value != null;
    }

    /**
     * 所謂數組的循環
     * 在循環的時候,若是遇到內部還嵌套了循環,會執行該方法
     * @param key 健
     * @return 空或者另外一個實現了郵件合併接口的子類
     */
    @Override
    public IMailMergeDataSource getChildDataSource(String key) throws Exception {
        Object object = map.get(key);
        return object instanceof List ? new MailMapData(key, (List) object) : null;
    }
}

2,咱們實現一個 郵件合併的數據 操做類,用於加載模板 和 把數據填入模板,以及保存文件,具體看註解 

package com.hwq.aspose.word;

import com.aspose.words.Document;
import com.aspose.words.SaveFormat;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 幾個經常使用標籤,一下都基於域的郵件合併模式
 * 普通數據 «key» 或者 «key.kk»
 * 列表循環 «TableStart:key» ... «TableEnd:key»
 * 圖片標籤 «Image:key»
 */
public class WordData {

    private Document doc;

    /**
     * 構造器
     * @param doc Word 文檔操做類,操做模板的基礎
     */
    public WordData(Document doc) {
        this.doc = doc;
    }

    /**
     * 插入普通數據,即郵件合併的 編輯域內容爲 «key» 這種寫法的數據(少用或者不用)
     * @param keys   健數組
     * @param values 值數組
     */
    public void setData(String[] keys, Object[] values) {
        try {
            doc.getMailMerge().execute(keys, values);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex.getMessage());
        }
    }

    /**
     * 插入實體數據,即郵件合併的 編輯域內容爲 «key.kk» 這種寫法的數據(經常使用)
     * @param key 健值
     * @param map 單層樹狀的 Map 集合
     */
    public void setMap(String key, Map<String, Object> map) {
        Set<String> keySet = map.keySet();
        String[] keys = new String[keySet.size()];
        Object[] values = new Object[keySet.size()];
        int index = 0;
        for (String kk : keySet) {
            keys[index] = key + "." + kk;
            values[index] = map.get(kk);
            index ++;
        }
        try {
            doc.getMailMerge().execute(keys, values);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex.getMessage());
        }
    }

    /**
     * 插入列表數據,即郵件合併的 編輯域內容爲 «TableStart:key» ... «TableEnd:key» 這種寫法的數據(經常使用)
     * 理論上支持和 JSON 樹狀相同格式的無限樹狀 Map List 嵌套
     * @param key  健
     * @param list 數據列表
     */
    public void setList(String key, List<Map<String, Object>> list) {
        try {
            doc.getMailMerge().executeWithRegions(new MailMapData(key, list));
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex.getMessage());
        }
    }

    /**
     * 保存文件到本地
     * @param path   保存的地址
     * @param suffix 保存的後綴名 和 格式
     */
    public void save(String path, SuffixEnum suffix) {
        try {
            doc.save(path + suffix.getSuffix(), suffix.getFormat());
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex.getMessage());
        }
    }

}

3,咱們建立一個生成數據操做類的工具類

package com.hwq.aspose.word;

import com.aspose.words.Document;
import com.aspose.words.License;
import org.springframework.core.io.ClassPathResource;

import java.io.InputStream;

public class WordUtil {

    private static WordUtil asposeUtil = null;
    private final String TEMP_PATH = "export/license.xml";

    /**
     * 私有化構造器,並在類初始化的時候,執行加載證書的方法
     * 若是沒有證書,會有水印,測試階段先註釋
     */
    private WordUtil() {
        // loadLicense();
    }

    /**
     * 單例模式對外暴漏的獲取實體的方法
     */
    public static WordUtil get() {
        if (asposeUtil == null) {
            asposeUtil = new WordUtil();
        }
        return asposeUtil;
    }

    /**
     * 加載簽名證書的方法,購買正版的時候使用該方法加載
     * 爲了防止重複加載證書,該類設計爲單例模式
     */
    private void loadLicense() {
        boolean result = false;
        ClassPathResource license = new ClassPathResource(TEMP_PATH);
        License aposeLic = new License();
        try {
            aposeLic.setLicense(license.getInputStream());
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex.getMessage());
        }
    }

    /**
     * 初始化 WORD 模板並獲取郵件合併數據操做類
     * @param templatePath 模板完整路徑
     */
    public WordData initWordData(String templatePath) {
        try (
                InputStream in = new ClassPathResource(templatePath).getInputStream();
        ) {
            Document doc = new Document(in);
            return new WordData(doc);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex.getMessage());
        }
    }
}

 

4,咱們建立一個枚舉,用於肯定文件最後生成的格式,該枚舉不完整,後續可能補充

package com.hwq.aspose.word;

import com.aspose.words.SaveFormat;

/**
 * 該枚舉主要定義生成的文件的後綴名的內容格式
 * 後續根據須要自行添加,具體支持的格式查看 SaveFormat 類的源碼
 */
public enum SuffixEnum {

    DOCX(".docx", SaveFormat.DOCX),
    DOC(".doc", SaveFormat.DOC),
    PDF(".pdf", SaveFormat.PDF),
    PNG(".png", SaveFormat.PDF);

    public String suffix;
    public int format;

    SuffixEnum(String suffix, int saveFormat) {
        this.suffix = suffix;
        this.format = saveFormat;
    }

    public String getSuffix() {
        return suffix;
    }

    public int getFormat() {
        return format;
    }
}

5,一切準備就緒,讓咱們寫一個測試類來測試一下

package com.hwq.aspose.word;

import org.springframework.core.io.ClassPathResource;

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

public class Tester {

    public static void main(String[] args) throws Exception {

        // 模板的地址須要到後綴名爲止 和 最後生成文件的保存地址,後綴名不須要加
        String temp = "export/wps_word.docx";
        String path = "E:/text/file/" + UUID.randomUUID().toString().replaceAll("-", "");

        // 合併模板和數據
        WordData word = WordUtil.get().initWordData(temp);
        word.setMap("user", getMap());
        word.setList("data", getList());

        // 保存新文件,該方法的第二個參數決定保存文件的類型和格式
        word.save(path, SuffixEnum.DOCX);
        // word.save(file, SuffixEnum.PDF);
    }

    /**
     * 獲取圖片資源,這裏測試就用一張圖片,具體項目應該傳入不一樣地址
     * 這裏只要返回二進制數組就行,具體的操做不固定,網絡圖片也是可行的
     */
    public static byte[] getImage() throws Exception {
        InputStream in = new ClassPathResource("export/head.png").getInputStream();
        ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc = 0;
        while ((rc = in.read(buff, 0, 100)) > 0) {
            swapStream.write(buff, 0, rc);
        }
        return swapStream.toByteArray();
    }

    /**
     * 獲取單層樹狀結構數據
     */
    public static Map<String, Object> getMap() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("name", "Word");
        map.put("sex", "男");
        return map;
    }

    /**
     * 獲取多層樹狀結構數據 (2層),理論上支持無限嵌套
     */
    public static List<Map<String, Object>> getList() throws Exception {
        List<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
        for (int j = 0; j < 2; j++) {
            Map<String, Object> dataMap = new HashMap<String, Object>();
            List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
            for (int i = 0; i < 3; i++) {
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("code", i + 1);
                map.put("name", "000" + i);
                map.put("date", "1992071" + i);

                // 圖片須要的是二進制數組
                map.put("head", getImage());
                list.add(map);
            }
            dataMap.put("list", list);
            data.add(dataMap);
        }
        return data;
    }
}

 6,點擊運行,獲得結果,還算是比較順利的

四,一些問題的解釋

1,若是沒有購買使用,測試生成的文件會帶水印,而且有使用時間以及文件大小的侷限性,如上圖,就是測試的文件帶着大大的水印,

若是條件容許,建議去官網進行購買,購買後咱們能夠得到一個 license.xml 的簽名證書,在第三步的第3小步的類中把加載證書的代碼

解開,就能夠享受無水印無限制的結果了

固然,若是我麼只是爲了學習一下,使用破解版也是無可厚非的,不過不建議破解最新版,容易產生糾紛,筆者建議破解舊一點的版本,

這也是一開始爲何要下載兩個版本的緣由,至於破解的過程無非就是用 idea 打斷點,找到簽名的驗證代碼,反編譯修改成跳過驗證,

在打成 jar 包,網上教程不少,這裏再也不綴訴,下面筆者給出本身破譯的版本供你們學習(請勿用於商業用途),點擊前往

切換兩個版本 須要注意修改一下 第三步的 第 1 小步建立類,這是因爲兩個版本的 getValue 方法有所不一樣,具體看上面的代碼註釋

破解後的咱們能夠隨意編寫一個 license.xml 內容以下,直接複製就可使用

<License>
    <Data>
        <!-- 這裏放產品名稱 Aspose 的產品名稱,能夠多個 -->
        <Products>
            <Product>Aspose.Words for Java</Product>
        </Products>
        <!-- 這裏填寫產品類型,我的仍是企業,通常寫 Enterprise -->
        <EditionType>Enterprise</EditionType>
        <!-- 這裏是 訂閱到期期限,日期要大於當前 -->
        <SubscriptionExpiry>20991231</SubscriptionExpiry>
        <!-- 這裏是 許可證到期期限,日期要大於當前 -->
        <LicenseExpiry>20991231</LicenseExpiry>
        <!-- 流水號,隨便填一個,沒有中文就行 -->
        <SerialNumber>8bfe198c-7f0c-4ef8-8ff0-ac</SerialNumber>
    </Data>
    <!-- 簽名參數,隨便填一個,沒有中文那就行 -->
    <Signature>ZheLiSuiBianXieDianShaDouXing</Signature>
</License>

2,咱們會發現生成的文檔,每一行文字都比較靠上,這個困擾了筆者好久,通過反覆測試  發現用 WPS,編寫的模板新生成的文件的

每一段文字只要不設置段後間距,生成的新文件就會莫名多出 10 榜 的段後間距,不知道是程序的緣由仍是 wps 自己的緣由

解決辦法,改用 office 編寫的模板就一切正常了,推薦使用,畢竟 office 和 aspose 都是國外的軟件和產品

若是非要用 wps 編寫模板,那就只能給每一段文字設置段後間距爲 1 榜,這樣就不會,也能夠試試比較新版的 WPS

相關文章
相關標籤/搜索