程式員架構進階
一 背景
最近在項目開發中,有資料導出到word的需求。這就涉及代碼生成word文檔的操作,且有格式要求。大家用word做過履歷的都有了解,做履歷時,會使用表格、圖檔、文字等元素。而且表格也可能有嵌套、合并單元格,以及插入圖檔到單元格的操作。該怎麼做?
二 Java操作Office方案
百度一下Java Office操作,或者再直接一點搜尋Java word,就比較容易搜到iText、POI等元件。在文章 Java導出word的幾種方式 這篇文章中,提到了包括Jacob、Apache POI、Java2word、iText、FreeMarker五種方式。
通過對比,結合需求要求,最終選擇了Apache POI來實作,是以這裡先詳細介紹POI,以及一個可用的demo,供參考。
三 ApachePOI
Apache POI(官網)是基于Office Open XML标準(OOXML)和Microsoft的OLE 2複合文檔格式(OLE2)處理各種檔案格式的開源項目。簡而言之,您可以使用Java讀寫MS Excel檔案,可以使用Java讀寫MS Word和MS PowerPoint檔案。
poi的gitee位址:gitee。入門教程可以參考 Apache POI Word(docx) 入門示例教程。
四 版本資訊
poi的最新版本已經到了5.0.0,不過可以找到的大部分demo都是基于3.x版本或4.1版本。為了盡快搭建demo并運作起來,我們也沒有使用最新版本,而是選擇了4.1.0進行開發。
4.1 引用依賴
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<poi.version>4.1.0</poi.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi.version}</version>
</dependency>
<!-- poi處理xlsx格式,用于處理word中的表格 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-excelant</artifactId>
<version>${poi.version}</version>
</dependency>
<!-- poi-tl基于poi的word模闆引擎 -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
4.2 建立word示例代碼
4.2.1 建立新的文檔
建立word文檔比較簡單,直接使用new XWPFDocument即可,XWPFDocument是對 .docx 文檔操作的進階封裝API:
XWPFDocument doc = new XWPFDocument();
4.2.2 表格
即Word文檔中的表格。API建立時需要指定行數和列數,示例如下:
//建立一個表格,并指定寬度
XWPFTable table = doc.createTable(4, 4);
TableTools.widthTable(table, MiniTableRenderData.WIDTH_A4_FULL, 4);
//設定第0行資料
List<XWPFTableCell> row0 = table.getRow(0).getTableCells();
row0.get(0).setText("xxxx"); //為第0行第0列設定内容
row0.get(0).setWidth("200");
row0.get(1).setText("aaaa");
row0.get(2).setText("bbbb");
row0.get(3).setText("cccc");
正常的簡單表格,我們隻要按照上述代碼逐行操作即可;但現實中不會這麼容易。通常會涉及在單元格插入圖檔、合并行、合并列,甚至表格嵌套。目前表格嵌套暫未實作,先介紹其他三種情況。
4.2.3 列合并
有兩種方法,一種是使用addNewHMerge方法,通過設定合并的起始列和結束列,逐個列進行合并:
List<XWPFTableCell> row2_1 = table.getRow(2).getTableCells();
row2_1.get(0).setText("合并表格"); //為第0行第0列設定内容
//将第一列到第四列合并
for (int i = 1; i <= 3; i++) {
//對單元格進行合并的時候,要标志單元格是否為起點,或者是否為繼續合并
if (i == 1)
row2_1.get(i).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);//這是起點
else
row2_1.get(i).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);//繼續合并
}
在業務代碼中這樣的寫法稍顯繁瑣,我們也可以直接使用TableTools.mergeCellsHorizonal()函數來執行合并:
// 合并第一行的第0列到第8列單元格
TableTools.mergeCellsHorizonal(table, 1, 0, 8);
4.2.4 行合并
如果是要合并某幾行,也可以使用TableTools提供的方法:
// 合并第0列的第一行到第九行的單元格
TableTools.mergeCellsVertically(table, 0, 1, 9);
我們看一下TableTools的源碼:
public static void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {
if (toRow > fromRow) {
for(int rowIndex = fromRow; rowIndex <= toRow; ++rowIndex) {
XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
CTTcPr tcPr = getTcPr(cell);
CTVMerge vMerge = tcPr.addNewVMerge();
if (rowIndex == fromRow) {
vMerge.setVal(STMerge.RESTART);
} else {
vMerge.setVal(STMerge.CONTINUE);
}
}
}
}
可以發現,底層還是使用addNewVMerge等方法,也設定了起始和結束位置,隻是做了一層封裝。
4.2.5 圖檔插入表格
圖檔插入表格要麻煩一些,如果大家在百度上搜尋過插入圖檔到表格方法,大機率會找到這樣的操作:
大部分對應的都是3.9以前的版本,寫起來比較複雜,而且在4.x之後,圖中super.getRelationId()方法也發生了變化,代碼報錯。
通過調研,發現XWPFRun中提供了addPicture方法,寫起來也簡單了很多。一個示例如下:
String imageFile = "/Users/xxx/Downloads/圖檔 1.png";
InputStream stream = new FileInputStream(imageFile);
//表格中建立段落
XWPFParagraph paragraph = row2_1.get(1).getParagraphs().get(0);
XWPFRun run = paragraph.createRun();
run.addPicture(stream, XWPFDocument.PICTURE_TYPE_PNG, "Generated",
Units.toEMU(364), Units.toEMU(256));
run.addPicture接收的參數依次為:圖檔的InputStream流,圖檔類型,圖檔名稱(非檔案名),圖檔寬度、圖檔高度。通過這個方法,我們就可以把圖檔插入到指定的表格中,并設定圖檔的寬高屬性。
五 總結
通過上述介紹,大家應該可以簡單實作一個表格了。本文的方式還是偏向于寫死的方式,在很多場景(例如履歷、報表等典型場景)可以采用模闆的方式,建立word模闆,然後用模闆内容替換來生成複雜樣式的表格。這個在後續文章中再做介紹,大家也可以先搜尋相關的實作來學習了解。