Android使用ApachePOI元件讀寫Worddoc和docx檔案【總結不錯】
最近在專案中要生成Word的doc和docx檔案,一番百度google之後,發現通過java語言實現的主流是Apache的POI元件。除了POI,這裡還有另一種實現,不過我沒有去研究,有興趣的同學可以研究研究。
關於POI可以訪問Apache POI的官網獲取詳細的資訊。
進入主題!
由於專案中只是用到了doc和docx的元件,下面也只是介紹這兩個元件的使用
一、在Android Studio中如何用POI元件
從POI官網上看,貌似暫並不支援IntelliJ IDE,如下圖,所以這裡我們採用直接下載jar包並匯入專案的方式。
官網how to build
通過官網 ->Overview->Components,可以看到 d和docx檔案分別對應著元件HWPF和XWPF,而HWPF和XWPF則對應著poi-scratchpad和poi-ooxml
檔案型別 | 元件名 | MavenId |
---|---|---|
doc | HWPF | poi-scratchpad |
docx | XWPF | poi-ooxml |
Components Map
下載
進入Apache下載頁面,選擇最新版下載,如下。選擇The latest beta release is Apache POI 3.16-beta2會跳轉到poi-bin-3.16-beta2-20170202.tar.gz,然後點選poi-bin-3.16-beta2-20170202.tar.gz,選擇映象後即可成功下載。
注
linux系統選擇.tar.gz
windows系統選擇.zip
POI下載頁面
解壓
將下載後的壓縮包解壓,會得到以下檔案。
檔案(夾)名 | 作用 |
---|---|
docs | 文件(包括API文件和如何使用及版本資訊) |
lib | doc功能實現依賴的包 |
ooxml-lib | docx功能實現依賴的包 |
LICENSE | |
NOTICE | |
poi-3.16-beta2.jar | The prerequisite poi-scratchpad-3.16-beta2.jar |
poi-examples-3.16-beta2.jar | 不明確 |
poi-excelant-3.16-beta2.jar | excel功能實現 |
poi-ooxml-3.16-beta2.jar | docx功能實現 |
poi-ooxml-schemas-3.16-beta2.jar | The prerequisite of poi-ooxml-3.16-beta2.jar |
poi-scratchpad-3.16-beta2.jar | doc功能實現 |
解壓後的poi包
匯入
不熟悉怎麼匯入的同學可以看看Android Studio匯入jar包教程
1、doc
對於doc檔案,需要將lib資料夾下jar包,poi-3.16-beta2.jar,poi-scratchpad-3.16-beta2.jar放入android專案libs目錄下(lib資料夾下的junit-4.12.jar和log4j-1.2.17.jar不放我的專案也沒出現異常,能少點是點)。
專案中的libs
2、docx
對於docx,需要匯入lib資料夾下jar包,poi-3.16-beta2.jar,poi-ooxml-3.16-beta2.jar,poi-ooxml-schemas-3.16-beta2.jar和ooxml-lib下的包,由於一直我這一直出現Warning:Ingoring InnerClasses attribute for an anonymous inner class的錯誤,同時由於doc基本滿足我的需求以及匯入這麼多jar導致apk體積增大,就沒有去實現。
有興趣的同學可以研究研究。
二、實現doc檔案的讀寫
Apache POI中的HWPF模組是專門用來讀取和生成doc格式的檔案。在HWPF中,我們使用HWPFDocument來表示一個word doc文件。在看程式碼之前,有必要了解HWPFDocument中的幾個概念:
名稱 | 含義 |
---|---|
Range | 表示一個範圍,這個範圍可以是整個文件,也可以是裡面的某個小節(Section),也可以是段落(Paragraph),還可以是擁有功能屬性的一段文字(CharacterRun) |
Section | word文件的一個小節,一個word文件可以由多個小節構成。 |
Paragraph | word文件的一個段落,一個小節可以由多個段落構成。 |
CharacterRun | 具有相同屬性的一段文字,一個段落可以由多個CharacterRun組成。 |
Table | 一個表格。 |
TableRow | 表格對應的行 |
TableCell | 表格對應的單元格 |
注意 : Section、Paragraph、CharacterRun和Table都繼承自Range。
讀寫前注意:Apache POI 提供的HWPFDocument類只能讀寫規範的.doc檔案,也就是說假如你使用修改 字尾名 的方式生成doc檔案或者直接以命名的方式建立,將會出現錯誤“Your file appears not to be a valid OLE2 document”
Invalid header signature; read 0x7267617266202E31, expected 0xE11AB1A1E011CFD0 - Your file appears not to be a valid OLE2 document
DOC讀
讀doc檔案有兩種方式
(a)通過WordExtractor讀檔案
(b)通過HWPFDocument讀檔案
在日常應用中,我們從word檔案裡面讀取資訊的情況非常少見,更多的還是把內容寫入到word檔案中。使用POI從word doc檔案讀取資料時主要有兩種方式:通過WordExtractor讀和通過HWPFDocument讀。在WordExtractor內部進行資訊讀取時還是通過HWPFDocument來獲取的。
使用WordExtractor讀
在使用WordExtractor讀檔案時我們只能讀到檔案的文字內容和基於文件的一些屬性,至於文件內容的屬性等是無法讀到的。如果要讀到文件內容的屬性則需要使用HWPFDocument來讀取了。下面是使用WordExtractor讀取檔案的一個示例:
//通過WordExtractor讀檔案
public class WordExtractorTest {
private final String PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "test.doc");
private static final String TAG = "WordExtractorTest";
private void log(Object o) {
Log.d(TAG, String.valueOf(o));
}
public void testReadByExtractor() throws Exception {
InputStream is = new FileInputStream(PATH);
WordExtractor extractor = new WordExtractor(is);
//輸出word文件所有的文字
log(extractor.getText());
log(extractor.getTextFromPieces());
//輸出頁首的內容
log("頁首:" + extractor.getHeaderText());
//輸出頁尾的內容
log("頁尾:" + extractor.getFooterText());
//輸出當前word文件的元資料資訊,包括作者、文件的修改時間等。
log(extractor.getMetadataTextExtractor().getText());
//獲取各個段落的文字
String paraTexts[] = extractor.getParagraphText();
for (int i=0; i<paraTexts.length; i++) {
log("Paragraph " + (i+1) + " : " + paraTexts[i]);
}
//輸出當前word的一些資訊
printInfo(extractor.getSummaryInformation());
//輸出當前word的一些資訊
this.printInfo(extractor.getDocSummaryInformation());
this.closeStream(is);
}
/**
* 輸出SummaryInfomation
* @param info
*/
private void printInfo(SummaryInformation info) {
//作者
log(info.getAuthor());
//字元統計
log(info.getCharCount());
//頁數
log(info.getPageCount());
//標題
log(info.getTitle());
//主題
log(info.getSubject());
}
/**
* 輸出DocumentSummaryInfomation
* @param info
*/
private void printInfo(DocumentSummaryInformation info) {
//分類
log(info.getCategory());
//公司
log(info.getCompany());
}
/**
* 關閉輸入流
* @param is
*/
private void closeStream(InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用HWPFDocument讀
HWPFDocument是當前Word文件的代表,它的功能比WordExtractor要強。通過它我們可以讀取文件中的表格、列表等,還可以對文件的內容進行新增、修改和刪除操作。只是在進行完這些新增、修改和刪除後相關資訊是儲存在HWPFDocument中的,也就是說我們改變的是HWPFDocument,而不是磁碟上的檔案。如果要使這些修改生效的話,我們可以呼叫HWPFDocument的write方法把修改後的HWPFDocument輸出到指定的輸出流中。這可以是原檔案的輸出流,也可以是新檔案的輸出流(相當於另存為)或其它輸出流。下面是一個通過HWPFDocument讀檔案的示例:
//使用HWPFDocument讀檔案
public class HWPFDocumentTest {
private final String PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "test.doc");
private static final String TAG = "HWPFDocumentTest";
private void log(Object o) {
Log.d(TAG, String.valueOf(o));
}
public void testReadByDoc() throws Exception {
InputStream is = new FileInputStream(PATH);
HWPFDocument doc = new HWPFDocument(is);
//輸出書籤資訊
this.printInfo(doc.getBookmarks());
//輸出文字
log(doc.getDocumentText());
Range range = doc.getRange();
//讀整體
this.printInfo(range);
//讀表格
this.readTable(range);
//讀列表
this.readList(range);
this.closeStream(is);
}
/**
* 關閉輸入流
* @param is
*/
private void closeStream(InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 輸出書籤資訊
* @param bookmarks
*/
private void printInfo(Bookmarks bookmarks) {
int count = bookmarks.getBookmarksCount();
log("書籤數量:" + count);
Bookmark bookmark;
for (int i=0; i<count; i++) {
bookmark = bookmarks.getBookmark(i);
log("書籤" + (i+1) + "的名稱是:" + bookmark.getName());
log("開始位置:" + bookmark.getStart());
log("結束位置:" + bookmark.getEnd());
}
}
/**
* 讀表格
* 每一個回車符代表一個段落,所以對於表格而言,每一個單元格至少包含一個段落,每行結束都是一個段落。
* @param range
*/
private void readTable(Range range) {
//遍歷range範圍內的table。
TableIterator tableIter = new TableIterator(range);
Table table;
TableRow row;
TableCell cell;
while (tableIter.hasNext()) {
table = tableIter.next();
int rowNum = table.numRows();
for (int j=0; j<rowNum; j++) {
row = table.getRow(j);
int cellNum = row.numCells();
for (int k=0; k<cellNum; k++) {
cell = row.getCell(k);
//輸出單元格的文字
log(cell.text().trim());
}
}
}
}
/**
* 讀列表
* @param range
*/
private void readList(Range range) {
int num = range.numParagraphs();
Paragraph para;
for (int i=0; i<num; i++) {
para = range.getParagraph(i);
if (para.isInList()) {
log("list: " + para.text());
}
}
}
/**
* 輸出Range
* @param range
*/
private void printInfo(Range range) {
//獲取段落數
int paraNum = range.numParagraphs();
log(paraNum);
for (int i=0; i<paraNum; i++) {
log("段落" + (i+1) + ":" + range.getParagraph(i).text());
if (i == (paraNum-1)) {
this.insertInfo(range.getParagraph(i));
}
}
int secNum = range.numSections();
log(secNum);
Section section;
for (int i=0; i<secNum; i++) {
section = range.getSection(i);
log(section.getMarginLeft());
log(section.getMarginRight());
log(section.getMarginTop());
log(section.getMarginBottom());
log(section.getPageHeight());
log(section.text());
}
}
/**
* 插入內容到Range,這裡只會寫到記憶體中
* @param range
*/
private void insertInfo(Range range) {
range.insertAfter("Hello");
}
}
DOC寫
使用HWPFDocument寫檔案
在使用POI寫word doc檔案的時候我們必須要先有一個doc檔案才行,因為我們在寫doc檔案的時候是通過HWPFDocument來寫的,而HWPFDocument是要依附於一個doc檔案的。所以通常的做法是我們先在硬碟上準備好一個內容空白的doc檔案,然後建立一個基於該空白檔案的HWPFDocument。之後我們就可以往HWPFDocument裡面新增內容了,然後再把它寫入到另外一個doc檔案中,這樣就相當於我們使用POI生成了word doc檔案。
//寫字串進word
InputStream is = new FileInputStream(PATH);
HWPFDocument doc = new HWPFDocument(is);
//獲取Range
Range range = doc.getRange();
for(int i = 0; i < 100; i++) {
if( i % 2 == 0 ) {
range.insertAfter("Hello " + i + "\n");//在檔案末尾插入String
} else {
range.insertBefore(" Bye " + i + "\n");//在檔案頭插入String
}
}
//寫到原檔案中
OutputStream os = new FileOutputStream(PATH);
//寫到另一個檔案中
//OutputStream os = new FileOutputStream(其他路徑);
doc.write(os);
this.closeStream(is);
this.closeStream(os);
但是,在實際應用中,我們在生成word檔案的時候都是生成某一類檔案,該類檔案的格式是固定的,只是某些欄位不一樣罷了。所以在實際應用中,我們大可不必將整個word檔案的內容都通過HWPFDocument生成。而是先在磁碟上新建一個word文件,其內容就是我們需要生成的word檔案的內容,然後把裡面一些屬於變數的內容使用類似於“${paramName}”這樣的方式代替。這樣我們在基於某些資訊生成word檔案的時候,只需要獲取基於該word檔案的HWPFDocument,然後呼叫Range的replaceText()方法把對應的變數替換為對應的值即可,之後再把當前的HWPFDocument寫入到新的輸出流中。這種方式在實際應用中用的比較多,因為它不但可以減少我們的工作量,還可以讓文字的格式更加的清晰。下面我們就來基於這種方式做一個示例。
假設我們有個模板是這樣的:
doc模板
之後我們以該檔案作為模板,利用相關資料把裡面的變數進行替換,然後把替換後的文件輸出到另一個doc檔案中。具體做法如下:
public class HWPFTemplateTest {
/**
* 用一個doc文件作為模板,然後替換其中的內容,再寫入目標文件中。
* @throws Exception
*/
@Test
public void testTemplateWrite() throws Exception {
String templatePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "template.doc");
String targetPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "target.doc";
InputStream is = new FileInputStream(templatePath);
HWPFDocument doc = new HWPFDocument(is);
Range range = doc.getRange();
//把range範圍內的${reportDate}替換為當前的日期
range.replaceText("${reportDate}", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
range.replaceText("${appleAmt}", "100.00");
range.replaceText("${bananaAmt}", "200.00");
range.replaceText("${totalAmt}", "300.00");
OutputStream os = new FileOutputStream(targetPath);
//把doc輸出到輸出流中
doc.write(os);
this.closeStream(os);
this.closeStream(is);
}
/**
* 關閉輸入流
* @param is
*/
private void closeStream(InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 關閉輸出流
* @param os
*/
private void closeStream(OutputStream os) {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
三、實現docx檔案的讀寫
POI在讀寫word docx檔案時是通過xwpf模組來進行的,其核心是XWPFDocument。一個XWPFDocument代表一個docx文件,其可以用來讀docx文件,也可以用來寫docx文件。XWPFDocument中主要包含下面這幾種物件:
物件 | 含義 |
---|---|
XWPFParagraph | 代表一個段落 |
XWPFRun | 代表具有相同屬性的一段文字 |
XWPFTable | 代表一個表格 |
XWPFTableRow | 表格的一行 |
XWPFTableCell | 表格對應的一個單元格 |
同時XWPFDocument可以直接new一個docx檔案出來而不需要像HWPFDocument一樣需要一個模板存在。
具體可以參考這位同學寫的POI讀寫docx檔案。
四、總結
歡迎大家提出建議和糾正本文可能存在的錯誤之處,感謝支援。
作者:猿溼Xoong
連結:https://www.jianshu.com/p/8d23b7f54b8e
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授