Word文檔生成神器 poi-tl(poi-template-language)
github地址:https://github.com/Sayi/poi-tl
Word 模板引擎,基於Apache POI - the Java API for Microsoft Documents。
What is poi-tl
FreeMarker、Velocity基於文本模板和數據生成新的HTML頁面、配置文件等,poi-tl是Word模板引擎,基於Microsoft Word模板和數據生成新的文檔。
Word模板擁有豐富的樣式,poi-tl在生成的文檔中會完美保留模板中的樣式,還可以為標簽設置樣式,標簽的樣式會被應用到替換后的文本上,因此你可以專注於模板設計。
poi-tl是一種 "logic-less" 模板引擎,沒有復雜的控制結構和變量賦值,只有標簽,一些標簽可以被替換為文本、圖片、表格等,一些標簽會隱藏某些文檔內容,而另一些標簽則會將一系列文檔內容循環渲染。
"Powerful" constructs like variable assignment or conditional statements make it easy to modify the look of an application within the template system exclusively... however, at the cost of separation, turning the templates themselves into part of the application logic.
poi-tl支持自定義函數(插件),函數可以在Word模板的任何位置執行,在文檔的任何地方做任何事情(Do Anything Anywhere)是poi-tl的星辰大海。
Maven
<dependency>
<groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.9.1</version> </dependency>
2分鍾快速入門
從一個超級簡單的例子開始:把{{title}}
替換成"poi-tl 模板引擎"。
- 新建文檔模板
template.docx
,包含標簽{{title}}
- TDO模式:Template + data-model = output
//核心API采用了極簡設計,只需要一行代碼 XWPFTemplate.compile("template.docx").render(new HashMap<String, Object>(){{ put("title", "poi-tl 模板引擎"); }}).writeToFile("out_template.docx");
打開out_template.docx
文檔吧,一切如你所願。
標簽
標簽由前后兩個大括號組成,{{title}}
是標簽,{{?title}}
也是標簽,title
是這個標簽的名稱,?
標識了標簽類型,接下來我們來看看有哪些標簽類型。
文本
文本標簽是Word模板中最基本的標簽類型,{{name}}
會被數據模型中key為name
的值替換,如果找不到默認會清空標簽,可以配置是保留還是拋出異常。
文本標簽的樣式會應用到替換后的文本上,正如下面的例子所示。
數據:
{
"name": "Mama", "thing": "chocolates" }
Word模板:
{{name}} always said life was like a box of {{thing}}.{{name}} always said life was like a box of {{thing}}.
輸出:
Mama always said life was like a box of chocolates.Mama always said life was like a box of chocolates.
圖片
圖片標簽以@
開始,如{{@logo}}
會在數據中尋找key為logo
的值,然后將標簽替換成圖片。由於Word文檔中圖片不是由字符串表示(在文本型模板中,比如HTML網頁圖片是由字符串<img src="" />
表示),所以圖片標簽對應的數據有一定的結構要求,這些結構都會有相應的Java類對應。
數據:
{
"watermelon": { "image": "assets/watermelon.png", "pictureType" : "PNG" }, "lemon": { "image": "http://xxx/lemon.png", "pictureType" : "PNG" }, "banana": { "image": "sob.png", "pictureType" : "PNG", "width": 24, "height": 24 } }
Word模板:
Fruit Logo:
watermelon {{@watermelon}}
lemon {{@lemon}}
banana {{@banana}}
輸出:
Fruit Logo:
watermelon 🍉
lemon 🍋
banana 🍌
表格
表格標簽以#
開始,如{{#table}}
,它會被渲染成N行N列的Word表格,N的值取決於table
標簽的值。
數據:
{
"rows": [ { "cells": [ { "paragraphs": [ { "contents": [ { "text": "Song name" } ] } ] }, { "paragraphs": [ { "contents": [ { "text": "Artist" } ] } ] } ] } ] }
Word模板:
{{#song}}
輸出:
Song name | Artist |
列表
列表標簽對應Word的符號列表或者編號列表,以*
開始,如{{*number}}
。
數據:
{
"format" : { "lvlText" : "●" }, "items" : [ { "contents" : [ { "text" : "Plug-in grammar, add new grammar by yourself" } ] }, { "contents" : [ { "text" : "Supports word text, local pictures, web pictures, table, list, header, footer..." } ] }, { "contents" : [ { "text" : "Templates, not just templates, but also style templates" } ] } ] }
Word模板:
{{*feature}}
輸出:
● Plug-in function, define your own function
● Supports text, pictures, table, list, if, foreach...
● Templates, not just templates, but also style templates
區塊對
區塊對由前后兩個標簽組成,開始標簽以?
標識,結束標簽以/
標識,如{{?sections}}
作為sections區塊的起始標簽,{{/sections}}
為結束標簽,sections是這個區塊對的名稱。
區塊對在處理一系列文檔元素的時候非常有用,位於區塊對中的文檔元素(文本、圖片、表格等)可以被渲染零次,一次或N次,這取決於區塊對的取值。
False或空集合
如果區塊對的值是null
、false
或者空的集合,位於區塊中的所有文檔元素將不會顯示,類似於if語句的條件為false
。
數據:
{
"announce": false }
Word模板:
Made it,Ma!{{?announce}}Top of the world!{{/announce}}
Made it,Ma!
{{?announce}}
Top of the world!🎋
{{/announce}}
輸出:
Made it,Ma!
Made it,Ma!
非False且不是集合
如果區塊對的值不為null
、false
,且不是集合,位於區塊中的所有文檔元素會被渲染一次,if語句的條件為true
。
數據:
{
"person": { "name": "Sayi" } }
Word模板:
{{?person}}
Hi {{name}}!
{{/person}}
輸出:
Hi Sayi!
非空集合
如果區塊對的值是一個非空集合,區塊中的文檔元素會被迭代渲染一次或者N次,這取決於集合的大小,類似於foreach語法。
數據:
{
"songs": [ { "name": "Memories" }, { "name": "Sugar" }, { "name": "Last Dance(伍佰)" } ] }
Word模板:
{{?songs}}
{{name}}
{{/songs}}
輸出:
Memories
Sugar
Last Dance(伍佰)
在循環中可以通過一個特殊的標簽{{=#this}}
直接引用當前迭代的對象。
數據:
{
"produces": [ "application/json", "application/xml" ] }
Word模板:
{{?produces}}
{{=#this}}
{{/produces}}
輸出:
application/json
application/xml
嵌套
嵌套是在Word模板中引入另一個Word模板,可以理解為import、include或者word文檔合並,以+
標識,如{{+nested}}
。
數據:
{
"nested": { "file": "template/sub.docx", "dataModels": [ { "addr": "Hangzhou,China" }, { "addr": "Shanghai,China" } ] } }
給定兩個WordWord模板:
main.docx:
Hello, World
{{+nested}}
template/sub.docx:
Address: {{addr}}
輸出:
Hello, World
Address: Hangzhou,China
Address: Shanghai,China
詳細文檔與示例
更多的示例以及所有示例的源碼參見JUnit單元測試。
Contributing貢獻
你可以有很多途徑加入這個項目,不限於以下方式:
- 反饋使用中遇到的問題
- 分享成功的喜悅
- 更新和完善文檔
- 解決和討論Issue
建議和完善
參見常見問題,歡迎在GitHub Issue中提問和交流。
社區交流討論群:Gitter頻道
--------------------------------------------------
EasyExcel
github地址:https://github.com/alibaba/easyexcel
官方網站: https://yuque.com/easyexcel
因為公司不方便用QQ,所以建議加釘釘群
JAVA解析Excel工具EasyExcel
Java解析、生成Excel比較有名的框架有Apache poi、jxl。但他們都存在一個嚴重的問題就是非常的耗內存,poi有一套SAX模式的API可以一定程度的解決一些內存溢出的問題,但POI還是有一些缺陷,比如07版Excel解壓縮以及解壓后存儲都是在內存中完成的,內存消耗依然很大。easyexcel重寫了poi對07版Excel的解析,能夠原本一個3M的excel用POI sax依然需要100M左右內存降低到幾M,並且再大的excel不會出現內存溢出,03版依賴POI的sax模式。在上層做了模型轉換的封裝,讓使用者更加簡單方便
64M內存1分鍾內讀取75M(46W行25列)的Excel
相關文檔
維護者
玉霄、庄家鉅、懷宇
快速開始
讀Excel
/**
* 最簡單的讀 * <p>1. 創建excel對應的實體對象 參照{@link DemoData} * <p>2. 由於默認一行行的讀取excel,所以需要創建excel一行一行的回調監聽器,參照{@link DemoDataListener} * <p>3. 直接讀即可 */ @Test public void simpleRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 這里 需要指定讀用哪個class去讀,然后讀取第一個sheet 文件流會自動關閉 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); }
寫Excel
/**
* 最簡單的寫 * <p>1. 創建excel對應的實體對象 參照{@link com.alibaba.easyexcel.test.demo.write.DemoData} * <p>2. 直接寫即可 */ @Test public void simpleWrite() { String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx"; // 這里 需要指定寫用哪個class去讀,然后寫到第一個sheet,名字為模板 然后文件流會自動關閉 // 如果這里想使用03 則 傳入excelType參數即可 EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); }
web上傳、下載
/**
* 文件下載(失敗了會返回一個有部分數據的Excel) * <p> * 1. 創建excel對應的實體對象 參照{@link DownloadData} * <p> * 2. 設置返回的 參數 * <p> * 3. 直接寫,這里注意,finish的時候會自動關閉OutputStream,當然你外面再關閉流問題不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { // 這里注意 有同學反應使用swagger 會導致各種問題,請直接用瀏覽器或者用postman response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); // 這里URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關系 String fileName = URLEncoder.encode("測試", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); } /** * 文件上傳 * <p>1. 創建excel對應的實體對象 參照{@link UploadData} * <p>2. 由於默認一行行的讀取excel,所以需要創建excel一行一行的回調監聽器,參照{@link UploadDataListener} * <p>3. 直接讀即可 */ @PostMapping("upload") @ResponseBody public String upload(MultipartFile file) throws IOException { EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); return "success"; }