根據模板動態生成word(一)使用freemarker生成word

2023-07-10 09:02:01

@

一、準備模板

1、建立模板檔案

首先先建立一個word檔案,輸入模板內容freemaker的內容,下面是本次演示的word檔案。

然後將word檔案另存為 .xml 檔案,然後再把檔案字尾改成.ftl 。將專案的resource目錄下建立一個templates目錄(非必須步驟)將 模板檔案放到templates目錄下


開啟模板檔案按 Ctrl + Shift + L 將模板內容格式化。

2、處理模板

2.1 處理普通文字

處理文字比較簡單,將需要替換文字中直接用預留位置 ${} 替換即可。

這裡發現一個問題因為之前在word格式時我就已經替換了變數,但是在ftl變數卻被 拆分成多段了(見圖1)。但是這樣是freemaker是無法成功替換變數的,所以需要手動處理成到一個段裡(如圖2),關於這點實在太無語了,因為沒有找到比較好的處理辦法,只能手工處理,在實際的開發工作中曾經花了幾個小時來做這件事情。
圖1:

圖2

2.2 處理表格

如果模板裡需要用變數填充表格,建議模板裡的表格像word檔案一樣建一個兩行的表格。在模板中<<w:tbl>> 表示一個表格 、<w: tr> 表示一行、<w: tc> 表示一列。因為FreeMarker 是利用列表一行一行迴圈填充的,所以我們可以根據關鍵字找到<<w:tbl>>標籤,因為第一個 <w: tr>是表頭注意不要改到了,找到第二個<w: tr>在前後分別加上如下語句即可,後面的表格裡後面的行<w: tr>需要刪掉,建議模板裡的表格像word檔案一樣建一個兩行的表格即可這樣就不用刪了:

<#list itemList as item>
</#list>

替換後的模板如下:

2.3 處理圖片

如果模板裡需要用變數填充圖片,建議先在word檔案裡插入一張圖片,這樣在模板檔案裡找到<pkg:binaryData>標籤直接裡面把裡面的圖片base64字元替換成變數即可,word裡可以通過植入base64字元來展示圖片:

替換前:

替換後:

<pkg:binaryData>${image1}</pkg:binaryData>

到此模板已經調整完成,接下來就可以開始寫程式碼了。

二、專案程式碼

1、引入依賴

在專案的pom檔案裡引入如下依賴

	<dependency>
          <groupId>org.freemarker</groupId>
          <artifactId>freemarker</artifactId>
          <version>2.3.31</version>
      </dependency>

2、生成程式碼

將圖片轉成Base64字串的公共方法:

public static String getImageBase64Str(String imgFile) {
        try( InputStream in = new FileInputStream(imgFile)) {
            byte[] data = new byte[in.available()];
            in.read(data);
            BASE64Encoder encoder = new BASE64Encoder();
            return encoder.encode(data);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

根據模板檔案生成word,主要生成的word的檔案字尾必須是doc不能是docx,不然生成的檔案無法開啟。

public static void crateWord(Map<String, Object> dataMap, String templatePath, String targetFile){
        String path = templatePath.substring(0,templatePath.lastIndexOf("/"));
        String templateName = templatePath.substring(templatePath.lastIndexOf("/") + 1);
        try (FileOutputStream out = new FileOutputStream(targetFile);
             Writer writer = new BufferedWriter(new OutputStreamWriter(out, "utf-8"))){
            Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
            configuration.setDefaultEncoding("utf-8");
            configuration.setClassForTemplateLoading(FreeMakerTest.class, path);
            //除了ClassForTemplateLoading外,另一種模板載入方式DirectoryForTemplateLoading,也可用
            //ClassPathResource resource = new ClassPathResource(path);
            //configuration.setDirectoryForTemplateLoading(resource.getFile());
            //載入模板
            Template template = configuration.getTemplate(templateName);
            //渲染模板
            template.process(dataMap, writer);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (TemplateException e) {
            throw new RuntimeException(e);
        }
    }

三、驗證生成word

新建的列表資料實體類:

public class Arrears{
    private String name;
    private Integer num;

    private String endDay;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public String getEndDay() {
        return endDay;
    }

    public void setEndDay(String endDay) {
        this.endDay = endDay;
    }
}

準備模板填充資料

private static Map<String, Object> prepareParam(){
        LocalDate currentDate = LocalDate.now();
        LocalDate endDate = currentDate.plusYears(1L);
        List<Arrears> arrearsList = new ArrayList<>();
        arrearsList.add(new Arrears(){{setName("一頓老魏");setNum(1);setEndDay("三月內");}});
        arrearsList.add(new Arrears(){{setName("貴州大黃牛");setNum(1);setEndDay("一年內");}});
        arrearsList.add(new Arrears(){{setName("v我50");setNum(1);setEndDay("一月內");}});

        //填充所需要的資料
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("debtor", "陳有楚");
        dataMap.put("nowYear", String.valueOf(currentDate.getYear()));
        dataMap.put("nowMonth", String.valueOf(currentDate.getMonthValue()));
        dataMap.put("nowDay", String.valueOf(currentDate.getDayOfMonth()));
        dataMap.put("arrears", "一頓老魏、貴州大黃牛、v我50");
        dataMap.put("endYear", String.valueOf(endDate.getYear()));
        dataMap.put("endMonth", String.valueOf(endDate.getMonthValue()));
        dataMap.put("endDay", String.valueOf(endDate.getDayOfMonth()));
        dataMap.put("arrearsList", arrearsList);
        dataMap.put("image1", getImageBase64Str("D:\\picture\\其他\\24-05-23-142418.png"));
        dataMap.put("creditor", "知北遊");
        return dataMap;
    }

測試程式碼:

public static void main(String[] args) throws IOException {
        //準備引數
        Map<String, Object> dataMap = prepareParam();
        crateWord(dataMap,"/templates/qiantiao.ftl","D:\\test\\qiantiao.doc");
    }

測試結果: