最近在項目中要生成Word的doc和docx文件,一番百度google以後,發現經過java語言實現的主流是Apache的POI組件。除了POI,這裏還有另外一種實現,不過我沒有去研究,有興趣的同窗能夠研究研究。html
關於POI能夠訪問Apache POI的官網獲取詳細的信息。java
進入主題!linux
因爲項目中只是用到了doc和docx的組件,下面也只是介紹這兩個組件的使用android
從POI官網上看,貌似暫並不支持IntelliJ IDE,以下圖,因此這裏咱們採用直接下載jar包並導入項目的方式。apache
官網how to buildwindows
經過官網 ->Overview->Components,能夠看到 d和docx文件分別對應着組件HWPF和XWPF,而HWPF和XWPF則對應着poi-scratchpad和poi-ooxmlapi
文件類型 | 組件名 | MavenId |
---|---|---|
doc | HWPF | poi-scratchpad |
docx | XWPF | poi-ooxml |
Components Mapapp
進入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,選擇鏡像後便可成功下載。ui
注
linux系統選擇.tar.gz
windows系統選擇.zipthis
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包教程
一、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
二、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體積增大,就沒有去實現。
有興趣的同窗能夠研究研究。
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。
Invalid header signature; read 0x7267617266202E31, expected 0xE11AB1A1E011CFD0 - Your file appears not to be a valid OLE2 document
讀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"); } }
使用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(); } } } }
POI在讀寫word docx文件時是經過xwpf模塊來進行的,其核心是XWPFDocument。一個XWPFDocument表明一個docx文檔,其能夠用來讀docx文檔,也能夠用來寫docx文檔。XWPFDocument中主要包含下面這幾種對象:
對象 | 含義 |
---|---|
XWPFParagraph | 表明一個段落 |
XWPFRun | 表明具備相同屬性的一段文本 |
XWPFTable | 表明一個表格 |
XWPFTableRow | 表格的一行 |
XWPFTableCell | 表格對應的一個單元格 |
具體能夠參考這位同窗寫的POI讀寫docx文件。
歡迎你們提出建議和糾正本文可能存在的錯誤之處,感謝支持。
做者:猿溼Xoong 連接:https://www.jianshu.com/p/8d23b7f54b8e 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。