天天看點

Android中如何讀寫Word doc/ docx和PDF檔案?

最近在項目中要生成word的doc和docx檔案,一番百度google之後,發現通過java語言實作的主流是apache的poi元件。除了poi,這裡還有 另一種實作 ,不過我沒有去研究,有興趣的同學可以研究研究。

關于 poi 可以通路 apache poi的官網 擷取詳細的資訊。

進入主題!

由于項目中隻是用到了doc和docx的元件,下面也隻是介紹這兩個元件的使用

一、在android studio中如何用poi元件

從poi官網上看,貌似暫并不支援intellij ide,如下圖,是以這裡我們采用直接下載下傳jar包并導入項目的方式。

Android中如何讀寫Word doc/ docx和PDF檔案?

通過 官網 ->overview->components,可以看到 d和docx檔案分别對應着元件 hwpf 和xwpf ,而hwpf和xwpf則對應着poi-scratchpad和poi-ooxml

Android中如何讀寫Word doc/ docx和PDF檔案?
Android中如何讀寫Word doc/ docx和PDF檔案?

下載下傳

進入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中如何讀寫Word doc/ docx和PDF檔案?

解壓

将下載下傳後的壓縮包解壓,會得到以下檔案。

Android中如何讀寫Word doc/ docx和PDF檔案?
Android中如何讀寫Word doc/ docx和PDF檔案?

導入

不熟悉怎麼導入的同學可以看看 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不放我的項目也沒出現異常,能少點是點)。

Android中如何讀寫Word doc/ docx和PDF檔案?

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中的幾個概念:

Android中如何讀寫Word doc/ docx和PDF檔案?

注意 : 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寫入到新的輸出流中。這種方式在實際應用中用的比較多,因為它不但可以減少我們的工作量,還可以讓文本的格式更加的清晰。下面我們就來基于這種方式做一個示例。

假設我們有個模闆是這樣的:

Android中如何讀寫Word doc/ docx和PDF檔案?

之後我們以該檔案作為模闆,利用相關資料把裡面的變量進行替換,然後把替換後的文檔輸出到另一個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中主要包含下面這幾種對象:

Android中如何讀寫Word doc/ docx和PDF檔案?

同時xwpfdocument可以直接new一個docx檔案出來而不需要像hwpfdocument一樣需要一個模闆存在。

具體可以參考這位同學寫的 poi讀寫docx檔案 。

四、總結

歡迎大家提出建議和糾正本文可能存在的錯誤之處,感謝支援。

本文作者:佚名

來源:51cto

繼續閱讀