java生成word文檔


java生成word文檔

最近得到一個需求:按用戶提供的模板生成分析報告,並讓用戶可以在網頁上導出。這個功能以前沒做過,但是好像聽說過freemarker。於是乎,開始了我的百度之旅。

一、word文檔的本質

我也是最近才知道,word文檔的本質原來是一個壓縮文件。不信你看,將.docx文件修改文件后綴為.zip

然后解壓縮得到了這些文件,這些就是組成word文檔的所有文件。其中word文件夾下是主要內容

​其中,document.xml中是關於文檔內容的設置,相當於網頁里面的html文件一樣。_rels文件夾下的document.xml.rels文件是圖片配置信息。media文件夾下是文檔中所有圖片的文件,其他的應該是類似於網頁里面的CSS文件,設置樣式的。所以document.xml就是我們要修改的了。這樣的操作就相當於網頁已經編寫好了,只差從后台傳送數據到前端展示了。

二、創建freemarker模板

freemarker是一個模板引擎,百度是這樣介紹的:

FreeMarker是一款模板引擎: 即一種基於模板和要改變的數據, 並用來生成輸出文本(HTML網頁、電子郵件配置文件源代碼等)的通用工具。 它不是面向最終用戶的,而是一個Java類庫,是一款程序員可以嵌入他們所開發產品的組件。

​說白了就是跟前端頁面的變量綁定是一樣的,用過vuejs的都知道,前端使用“{{name}}”雙括號括起來的變量可以通過傳值改變頁面上數據。freemarker也是這樣,通過在document.xml中使用“${name}”dollar符大括號括起來的變量也可以通過傳值改變模板文件內容。清楚了這點就好辦了。

將document.xml文件放到IDEA項目中的templates文件夾下,然后按Ctrl+Alt+L鍵格式化xml內容,將需要動態修改的地方用“${}”括起來,如下

有的時候變量會被拆分成兩個,就要麻煩點把兩個中間的多余部分全都刪掉,然后在用符號括起來。這點相信大家都能理解。

以上模板創建就結束了,是不是很簡單。

三、代碼實現

模板搞定了,怎么根據模板生成文檔呢?關鍵步驟來了。

1.首先我們要將模板中的變量賦值,生成新的文件。

2.將生成的文件寫入壓縮文件。上面已經說了,word文檔本質就是壓縮文件。

3.將.docx文檔的其他內容也寫入壓縮文件。

4.將壓縮文件寫入word文檔,這就是最后生成的文檔。

導入依賴:

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

主要用到下面的工具類:


public class FreeMarkUtils {
    private static Logger logger = LoggerFactory.getLogger(FreeMarkUtils.class);

    public static Configuration getConfiguration(){
        //創建配置實例
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
        //設置編碼
        configuration.setDefaultEncoding("utf-8");
        configuration.setClassForTemplateLoading(FreeMarkUtils.class, "/templates");//此處設置模板存放文件夾名稱
        return configuration;
    }

    /**
     * 獲取模板字符串輸入流
     * @param dataMap   參數,鍵為綁定變量名,值為變量值
     * @param templateName  模板名稱
     * @return
     */
    public static ByteArrayInputStream getFreemarkerContentInputStream(Map dataMap, String templateName) {
        ByteArrayInputStream in = null;
        try {
            //獲取模板
            Template template = getConfiguration().getTemplate(templateName);
            StringWriter swriter = new StringWriter();
            //生成文件
            template.process(dataMap, swriter);
            in = new ByteArrayInputStream(swriter.toString().getBytes("utf-8"));//這里一定要設置utf-8編碼 否則導出的word中中文會是亂碼
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("模板生成錯誤!");
        }
        return in;
    }
    //outputStream 輸出流可以自己定義 瀏覽器或者文件輸出流
    public static void createDocx(Map dataMap, OutputStream outputStream) {
        ZipOutputStream zipout = null;
        try {
            //內容模板,傳值生成新的文件輸入流documentInput
            ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, "document.xml");
            //最初設計的模板,原word文件生成File對象
            File docxFile = new File(WordUtils.class.getClassLoader().getResource("templates/demo.docx").getPath());//模板文件名稱
            if (!docxFile.exists()) {
                docxFile.createNewFile();
            }
            ZipFile zipFile = new ZipFile(docxFile);//獲取原word文件的zip文件對象,相當於解壓縮了word文件
            Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();//獲取壓縮文件內部所有內容
            zipout = new ZipOutputStream(outputStream);
            //開始覆蓋文檔------------------
            int len = -1;
            byte[] buffer = new byte[1024];
            while (zipEntrys.hasMoreElements()) {//遍歷zip文件內容
                ZipEntry next = zipEntrys.nextElement();
                InputStream is = zipFile.getInputStream(next);
                if (next.toString().indexOf("media") < 0) {
                    zipout.putNextEntry(new ZipEntry(next.getName()));//這步相當於創建了個文件,下面是將流寫入這個文件
                    if ("word/document.xml".equals(next.getName())) {//如果是word/document.xml由我們輸入
                        if (documentInput != null) {
                            while ((len = documentInput.read(buffer)) != -1) {
                                zipout.write(buffer, 0, len);
                            }
                            documentInput.close();
                        }
                    } else {
                        while ((len = is.read(buffer)) != -1) {
                            zipout.write(buffer, 0, len);
                        }
                        is.close();
                    }
                }else{//這里設置圖片信息,針對要顯示的圖片
                    zipout.putNextEntry(new ZipEntry(next.getName()));
                        while ((len = is.read(buffer)) != -1) {
                            zipout.write(buffer, 0, len);
                        }
                        is.close();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("word導出失敗:"+e.getStackTrace());
        }finally {
            if(zipout!=null){
                try {
                    zipout.close();
                } catch (IOException e) {
                    logger.error("io異常");
                }
            }
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    logger.error("io異常");
                }
            }
        }
    }
}

四、插入圖片

​通過傳值已經可以基本完成生成文檔的功能了,但是用戶還要求要在文檔中生成統計分析圖。知道文檔本質的我馬上想出了辦法,但是這個就稍微有點麻煩了。剛剛說了,media文件夾下存放的是文檔中所有的圖片。我可以在word文檔要生成統計圖的地方先用圖片占好位置,調好大小。然后解壓后看這個圖片在media文件夾中叫什么名字。最后在代碼生成的時候,跳過原文件圖片的寫入替換成我生成的圖片就可以了。核心代碼如下:

  while (zipEntrys.hasMoreElements()) {
                ZipEntry next = zipEntrys.nextElement();
                InputStream is = zipFile.getInputStream(next);
                if (next.toString().indexOf("media") < 0) {
                   ...省略
                }else{//這里設置圖片信息,針對要顯示的圖片
                    zipout.putNextEntry(new ZipEntry(next.getName()));
                    if(next.getName().indexOf("image1.png")>0){//例如要用一張圖去替換模板中的image1.png
                        if(dataMap.get("image1")!=null){
                            byte[] bys = Base64Util.decode(dataMap.get("image1").toString());
                            zipout.write(bys,0,bys.length);
                        }else{
                            while ((len = is.read(buffer)) != -1) {
                                zipout.write(buffer, 0, len);
                            }
                            is.close();
                        }
                    }
                }
            }

注意:這個Base64Util是一個將圖片文件轉化為base64編碼字符串和將base64編碼字符串轉換為字節數組的工具類。這里涉及到圖片文件的本質,圖片的本質是一個二進制文件,二進制文件可以轉換成字節數組,而字節數組又可以和字符串互相轉換。這個知道就好,這里我把生成的圖片的字符串存入了集合中,寫入文檔的時候將字節數組寫入。這樣原圖片就被替換成了生成的圖片了。這里直接將新的圖片文件寫入也是一樣的效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM