java使用freemarker導出word文檔

使用freemarker導出word文檔

最近的項目中,在導出word文檔中遇見了一些問題,由於之前的導出word是導出的doc格式的word文檔,但是由於使用的freemarker技術,導出的word文檔其實就是xml文件,只是將文件後綴名改爲.doc其實實際上還是xml,所以在使用某些工具打開時,遇到無法解析,或者打開後直接顯示xml的問題。
所以這裏解決這個問題的辦法,是導出docx格式的文檔,因爲docx格式實際上就是zip格式,使用word生成的docx文檔,可以直接使用解壓縮工具打開,可以直接編輯裏面的內容。
這裏也展示一下怎麼導出doc格式的word文檔。

導出doc文件

先新建一個doc文件,製作好模板,這裏假設需要導出的是一個表格。製作好表格的格式,然後另存爲時保存爲xml文件。
在這裏插入圖片描述
另存爲之後,編輯xml文件,由於導出表格,這裏只有一行數據,需要修改爲freemarker的<#list>標籤來生成列表。將xml文件格式化之後,找到對應的李明這一行數據:

<w:tr>
    ......
    <w:tc>
        ......
        <w:p>
            ......
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="fareast"/>
                    <w:vertAlign w:val="baseline"/>
                    <w:lang w:fareast="ZH-CN"/>
                </w:rPr>
                <w:t>李明</w:t>
            </w:r>
        </w:p>
    </w:tc>
    <w:tc>
        ......
        <w:p>
           	......
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="fareast"/>
                    <w:vertAlign w:val="baseline"/>
                    <w:lang w:fareast="ZH-CN"/>
                </w:rPr>
                <w:t></w:t>
            </w:r>
        </w:p>
    </w:tc>
    <w:tc>
        ......
        <w:p>
            ......
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="fareast"/>
                    <w:vertAlign w:val="baseline"/>
                    <w:lang w:val="EN-US" w:fareast="ZH-CN"/>
                </w:rPr>
                <w:t>19</w:t>
            </w:r>
        </w:p>
    </w:tc>
</w:tr>

這裏省略了部分代碼,使用freemarker語法修改這一段xml文本:

<#list persons as person>    
    <w:tr>
        ......
        <w:tc>
            ......
            <w:p>
                ......
                <w:r>
                    <w:rPr>
                        <w:rFonts w:hint="fareast"/>
                        <w:vertAlign w:val="baseline"/>
                        <w:lang w:fareast="ZH-CN"/>
                    </w:rPr>
                    <w:t>${person.name}</w:t>
                </w:r>
            </w:p>
        </w:tc>
        <w:tc>
            ......
            <w:p>
               	......
                <w:r>
                    <w:rPr>
                        <w:rFonts w:hint="fareast"/>
                        <w:vertAlign w:val="baseline"/>
                        <w:lang w:fareast="ZH-CN"/>
                    </w:rPr>
                    <w:t>${person.gender}</w:t>
                </w:r>
            </w:p>
        </w:tc>
        <w:tc>
            ......
            <w:p>
                ......
                <w:r>
                    <w:rPr>
                        <w:rFonts w:hint="fareast"/>
                        <w:vertAlign w:val="baseline"/>
                        <w:lang w:val="EN-US" w:fareast="ZH-CN"/>
                    </w:rPr>
                    <w:t>${person.age}</w:t>
                </w:r>
            </w:p>
        </w:tc>
    </w:tr>
</#list>

修改完成之後,將文件後綴改爲ftl,將模板文件複製到項目中。之後就可以使用freemarker生成出xml文件,生成之後可以直接講文件名後綴名改爲doc。
這裏附上代碼(這裏是一個maven項目,模板文件在resources目錄下):
Person.class用來生成word文檔中的列表對象

public class Person {
    private String name;
    private String gender;
    private Integer age;

    public Person() {
    }

    public Person(String name, String gender, Integer age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    //......Getter、Setter方法省略
}    

數據生成的類(在生成docx的方法中依然使用該類)

public final class DataGenerate {
    private DataGenerate(){}

    public static List<Person> getData() {
        List<Person> persons = new ArrayList<>();
        persons.add(new Person("小李", "男", 31));
        persons.add(new Person("小王", "男", 18));
        persons.add(new Person("小紅", "女", 21));
        return persons;
    }
}

生成word文檔,這裏直接執行main方法即可

public class XmlToDoc {
    public static void main(String[] args) throws IOException, TemplateException {
        Configuration configuration = new Configuration(Configuration.getVersion());
        configuration.setDefaultEncoding("UTF-8");

        URL resource = XmlToDoc.class.getClassLoader().getResource("");
        if(resource != null){
            File resourceDir = new File(resource.getFile());
            configuration.setDirectoryForTemplateLoading(resourceDir);
            Template template = configuration.getTemplate("docTemplate.ftl");

            File docFile = new File(resource.getFile() + "out/導出.doc");
            File docDir = docFile.getParentFile();
            if(!docDir.exists()) {
                docDir.mkdirs();
            }
            FileOutputStream docFileOut = new FileOutputStream(docFile);
            BufferedWriter docWriter = new BufferedWriter(new OutputStreamWriter(docFileOut));
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("title", "標題");
            dataMap.put("persons", DataGenerate.getData());
            template.process(dataMap, docWriter);
            docWriter.flush();
            docWriter.close();
        }
    }
}

執行完成之後,生成的doc文檔在maven項目的target/classes/out目錄下。

導出docx文件

因爲項目之前使用的word導出都是使用的該技術,項目中已經做了很多freemarker模板,在這時替換其他技術的話,會產生很大的工作量。所以在這裏依然使用freemarker技術來導出word,用freemarker生成docx文件。使用解壓縮工具打開docx文件可以看到裏面有個word目錄,進去之後可以看到一個document.xml文件,這個文件就是word文檔的內容。
我們依然使用之前的word文檔,保存爲docx格式,解壓縮打開內部的word/document.xml文件。會發現文件與上面的導出xml文件有些不同,但是內檔內容部分<w:body>標籤中的內容基本一致。所以就嘗試這講wordxml的文件的<w:body>部分來替換docx的document.xml<w:body>部分。發現替換之後依然可以正常打開docx文件,所以這裏就直接講以前的模板保留<w:body>中的內容,其餘替換成docx的document.xml的內容來使用。至於導出word的代碼也需要修改,由於之前是直接導出xml格式,而這裏需要將生成的xml替換docx文件的document.xml文件,所以這裏多了一步zip文件操作。
與導出doc不同的是,這裏不僅需要一個ftl的freemarker模板,同時需要一個空的docx的文件(需要使用該文件來生成word)。
代碼如下:

public class XmlToDocx {
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static void main(String[] args) throws IOException, TemplateException {
        //使用模板生成document.xml文件
        Configuration configuration = new Configuration(Configuration.getVersion());
        configuration.setDefaultEncoding("UTF-8");

        URL resource = XmlToDoc.class.getClassLoader().getResource("");
        if(resource != null){
            File resourceDir = new File(resource.getFile());
            configuration.setDirectoryForTemplateLoading(resourceDir);
            Template template = configuration.getTemplate("docxTemplate.ftl");

            File tempFile = new File(resource.getFile() + "temp/docxTemp.xml");
            File tempDir = tempFile.getParentFile();
            if(!tempDir.exists()) {
                tempDir.mkdirs();
            }
            FileOutputStream tempFileOut = new FileOutputStream(tempFile);
            BufferedWriter tempWriter = new BufferedWriter(new OutputStreamWriter(tempFileOut));
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("title", "標題");
            dataMap.put("persons", DataGenerate.getData());
            template.process(dataMap, tempWriter);
            tempWriter.flush();
            tempWriter.close();

            //使用生成的xml文件來替換docx中的document.xml
            ZipFile docxTempFile = new ZipFile(resource.getFile() + "template.docx");
            File docxFile = new File(resource.getFile() + "out/導出.docx");
            File docxDir = docxFile.getParentFile();
            if(!docxDir.exists()){
                docxDir.mkdirs();
            }
            if(!docxFile.exists()) {
                docxFile.createNewFile();
            }
            ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(docxFile));

            Enumeration<? extends ZipEntry> entries = docxTempFile.entries();
            int len = 1;
            byte[] buffer = new byte[1024];
            while (entries.hasMoreElements()) {
                ZipEntry next = entries.nextElement();
                InputStream inputStream = docxTempFile.getInputStream(next);
                zipOut.putNextEntry(new ZipEntry(next.toString()));
                if("word/document.xml".equals(next.toString())) {
                    FileInputStream in = new FileInputStream(tempFile);
                    while ((len = in.read(buffer)) != -1){
                        zipOut.write(buffer, 0, len);
                    }
                    in.close();
                }else {
                    while ((len = inputStream.read(buffer)) != -1){
                        zipOut.write(buffer, 0, len);
                    }
                    inputStream.close();
                }
            }
            zipOut.close();

        }
    }
}

注意: 這裏使用doc另存爲的xml文本中的<w:body>內容來替換docx文件下word/document.xml文件中<w:body>內容,在我目前使用模板中是可以直接替換,但是不清楚是否有其他不兼容的情況,大家如果要這樣使用的話,自行驗證。但是直接用docx文件中的document.xml來直接製作模板,是可以直接使用上面的導出docx文件的方法的。

文中代碼: https://gitee.com/hello_ji/xml2word

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章