最近在項目中要生成word的doc和docx檔案,一番百度google之後,發現通過java語言實作的主流是apache的poi元件。除了poi,這裡還有 另一種實作 ,不過我沒有去研究,有興趣的同學可以研究研究。
關于 poi 可以通路 apache poi的官網 擷取詳細的資訊。
進入主題!
由于項目中隻是用到了doc和docx的元件,下面也隻是介紹這兩個元件的使用
一、在android studio中如何用poi元件
從poi官網上看,貌似暫并不支援intellij ide,如下圖,是以這裡我們采用直接下載下傳jar包并導入項目的方式。
通過 官網 ->overview->components,可以看到 d和docx檔案分别對應着元件 hwpf 和xwpf ,而hwpf和xwpf則對應着poi-scratchpad和poi-ooxml
下載下傳
進入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
解壓
将下載下傳後的壓縮包解壓,會得到以下檔案。
導入
不熟悉怎麼導入的同學可以看看 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不放我的項目也沒出現異常,能少點是點)。
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中的幾個概念:
注意 : 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
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";
} public void testreadbydoc() throws exception {
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 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());
}
* 讀清單
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
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,這裡隻會寫到記憶體中
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檔案中。具體做法如下:
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 os
private void closestream(outputstream os) { if (os != null) { try {
os.close();
三、實作docx檔案的讀寫
poi在讀寫word docx檔案時是通過xwpf子產品來進行的,其核心是xwpfdocument。一個xwpfdocument代表一個docx文檔,其可以用來讀docx文檔,也可以用來寫docx文檔。xwpfdocument中主要包含下面這幾種對象:
同時xwpfdocument可以直接new一個docx檔案出來而不需要像hwpfdocument一樣需要一個模闆存在。
具體可以參考這位同學寫的 poi讀寫docx檔案 。
四、總結
歡迎大家提出建議和糾正本文可能存在的錯誤之處,感謝支援。
本文作者:佚名
來源:51cto