使用 POI 讀寫 word doc 文件
目錄
1???? 讀 word doc 文件
1.1???? 通過 WordExtractor 讀文件
1.2???? 通過 HWPFDocument 讀文件
2???? 寫 word doc 文件
?
?????? Apache poi 的 hwpf 模塊是專門用來對(duì) word doc 文件進(jìn)行讀寫操作的。在 hwpf 里面我們使用 HWPFDocument 來表示一個(gè) word doc 文檔。在 HWPFDocument 里面有這么幾個(gè)概念:
l ? Range :它表示一個(gè)范圍,這個(gè)范圍可以是整個(gè)文檔,也可以是里面的某一小節(jié)( Section ),也可以是某一個(gè)段落( Paragraph ),還可以是擁有共同屬性的一段文本( CharacterRun )。
l ? Section : word 文檔的一個(gè)小節(jié),一個(gè) word 文檔可以由多個(gè)小節(jié)構(gòu)成。
l ? Paragraph : word 文檔的一個(gè)段落,一個(gè)小節(jié)可以由多個(gè)段落構(gòu)成。
l ? CharacterRun :具有相同屬性的一段文本,一個(gè)段落可以由多個(gè) CharacterRun 組成。
l ? Table :一個(gè)表格。
l ? TableRow :表格對(duì)應(yīng)的行。
l ? TableCell :表格對(duì)應(yīng)的單元格。
?????? Section 、 Paragraph 、 CharacterRun 和 Table 都繼承自 Range 。
1 ?????? 讀 word doc 文件
?????? 在日常應(yīng)用中,我們從 word 文件里面讀取信息的情況非常少見,更多的還是把內(nèi)容寫入到 word 文件中。使用 POI 從 word doc 文件讀取數(shù)據(jù)時(shí)主要有兩種方式:通過 WordExtractor 讀和通過 HWPFDocument 讀。在 WordExtractor 內(nèi)部進(jìn)行信息讀取時(shí)還是通過 HWPFDocument 來獲取的。
?
1.1 ???? 通過 WordExtractor 讀文件
?????? 在使用 WordExtractor 讀文件時(shí)我們只能讀到文件的文本內(nèi)容和基于文檔的一些屬性,至于文檔內(nèi)容的屬性等是無法讀到的。如果要讀到文檔內(nèi)容的屬性則需要使用 HWPFDocument 來讀取了。下面是使用 WordExtractor 讀取文件的一個(gè)示例:
public class HwpfTest { @SuppressWarnings("deprecation") @Test public void testReadByExtractor() throws Exception { InputStream is = new FileInputStream("D:\\test.doc"); WordExtractor extractor = new WordExtractor(is); //輸出word文檔所有的文本 System.out.println(extractor.getText()); System.out.println(extractor.getTextFromPieces()); //輸出頁眉的內(nèi)容 System.out.println("頁眉:" + extractor.getHeaderText()); //輸出頁腳的內(nèi)容 System.out.println("頁腳:" + extractor.getFooterText()); //輸出當(dāng)前word文檔的元數(shù)據(jù)信息,包括作者、文檔的修改時(shí)間等。 System.out.println(extractor.getMetadataTextExtractor().getText()); //獲取各個(gè)段落的文本 String paraTexts[] = extractor.getParagraphText(); for (int i=0; i<paraTexts.length; i++) { System.out.println("Paragraph " + (i+1) + " : " + paraTexts[i]); } //輸出當(dāng)前word的一些信息 printInfo(extractor.getSummaryInformation()); //輸出當(dāng)前word的一些信息 this.printInfo(extractor.getDocSummaryInformation()); this.closeStream(is); } /** * 輸出SummaryInfomation * @param info */ private void printInfo(SummaryInformation info) { //作者 System.out.println(info.getAuthor()); //字符統(tǒng)計(jì) System.out.println(info.getCharCount()); //頁數(shù) System.out.println(info.getPageCount()); //標(biāo)題 System.out.println(info.getTitle()); //主題 System.out.println(info.getSubject()); } /** * 輸出DocumentSummaryInfomation * @param info */ private void printInfo(DocumentSummaryInformation info) { //分類 System.out.println(info.getCategory()); //公司 System.out.println(info.getCompany()); } /** * 關(guān)閉輸入流 * @param is */ private void closeStream(InputStream is) { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }
?
?
1.2 ???? 通過 HWPFDocument 讀文件
?????? HWPFDocument 是當(dāng)前 Word 文檔的代表,它的功能比 WordExtractor 要強(qiáng)。通過它我們可以讀取文檔中的表格、列表等,還可以對(duì)文檔的內(nèi)容進(jìn)行新增、修改和刪除操作。只是在進(jìn)行完這些新增、修改和刪除后相關(guān)信息是保存在 HWPFDocument 中的,也就是說我們改變的是 HWPFDocument ,而不是磁盤上的文件。如果要使這些修改生效的話,我們可以調(diào)用 HWPFDocument 的 write 方法把修改后的 HWPFDocument 輸出到指定的輸出流中。這可以是原文件的輸出流,也可以是新文件的輸出流(相當(dāng)于另存為)或其它輸出流。下面是一個(gè)通過 HWPFDocument 讀文件的示例:
public class HwpfTest { @Test public void testReadByDoc() throws Exception { InputStream is = new FileInputStream("D:\\test.doc"); HWPFDocument doc = new HWPFDocument(is); //輸出書簽信息 this.printInfo(doc.getBookmarks()); //輸出文本 System.out.println(doc.getDocumentText()); Range range = doc.getRange(); // this.insertInfo(range); this.printInfo(range); //讀表格 this.readTable(range); //讀列表 this.readList(range); //刪除range Range r = new Range(2, 5, doc); r.delete();//在內(nèi)存中進(jìn)行刪除,如果需要保存到文件中需要再把它寫回文件 //把當(dāng)前HWPFDocument寫到輸出流中 doc.write(new FileOutputStream("D:\\test.doc")); this.closeStream(is); } /** * 關(guān)閉輸入流 * @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(); System.out.println("書簽數(shù)量:" + count); Bookmark bookmark; for (int i=0; i<count; i++) { bookmark = bookmarks.getBookmark(i); System.out.println("書簽" + (i+1) + "的名稱是:" + bookmark.getName()); System.out.println("開始位置:" + bookmark.getStart()); System.out.println("結(jié)束位置:" + bookmark.getEnd()); } } /** * 讀表格 * 每一個(gè)回車符代表一個(gè)段落,所以對(duì)于表格而言,每一個(gè)單元格至少包含一個(gè)段落,每行結(jié)束都是一個(gè)段落。 * @param range */ private void readTable(Range range) { //遍歷range范圍內(nèi)的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); //輸出單元格的文本 System.out.println(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()) { System.out.println("list: " + para.text()); } } } /** * 輸出Range * @param range */ private void printInfo(Range range) { //獲取段落數(shù) int paraNum = range.numParagraphs(); System.out.println(paraNum); for (int i=0; i<paraNum; i++) { // this.insertInfo(range.getParagraph(i)); System.out.println("段落" + (i+1) + ":" + range.getParagraph(i).text()); if (i == (paraNum-1)) { this.insertInfo(range.getParagraph(i)); } } int secNum = range.numSections(); System.out.println(secNum); Section section; for (int i=0; i<secNum; i++) { section = range.getSection(i); System.out.println(section.getMarginLeft()); System.out.println(section.getMarginRight()); System.out.println(section.getMarginTop()); System.out.println(section.getMarginBottom()); System.out.println(section.getPageHeight()); System.out.println(section.text()); } } /** * 插入內(nèi)容到Range,這里只會(huì)寫到內(nèi)存中 * @param range */ private void insertInfo(Range range) { range.insertAfter("Hello"); } }
?
?
2 ?????? 寫 word doc 文件
?????? 在使用 POI 寫 word doc 文件的時(shí)候我們必須要先有一個(gè) doc 文件才行,因?yàn)槲覀冊(cè)趯? doc 文件的時(shí)候是通過 HWPFDocument 來寫的,而 HWPFDocument 是要依附于一個(gè) doc 文件的。所以通常的做法是我們先在硬盤上準(zhǔn)備好一個(gè)內(nèi)容空白的 doc 文件,然后建立一個(gè)基于該空白文件的 HWPFDocument 。之后我們就可以往 HWPFDocument 里面新增內(nèi)容了,然后再把它寫入到另外一個(gè) doc 文件中,這樣就相當(dāng)于我們使用 POI 生成了 word doc 文件。
?????? 在實(shí)際應(yīng)用中,我們?cè)谏? word 文件的時(shí)候都是生成某一類文件,該類文件的格式是固定的,只是某些字段不一樣罷了。所以在實(shí)際應(yīng)用中,我們大可不必將整個(gè) word 文件的內(nèi)容都通過 HWPFDocument 生成。而是先在磁盤上新建一個(gè) word 文檔,其內(nèi)容就是我們需要生成的 word 文件的內(nèi)容,然后把里面一些屬于變量的內(nèi)容使用類似于“ ${paramName} ”這樣的方式代替。這樣我們?cè)诨谀承┬畔⑸? word 文件的時(shí)候,只需要獲取基于該 word 文件的 HWPFDocument ,然后調(diào)用 Range 的 replaceText() 方法把對(duì)應(yīng)的變量替換為對(duì)應(yīng)的值即可,之后再把當(dāng)前的 HWPFDocument 寫入到新的輸出流中。這種方式在實(shí)際應(yīng)用中用的比較多,因?yàn)樗坏梢詼p少我們的工作量,還可以讓文本的格式更加的清晰。下面我們就來基于這種方式做一個(gè)示例。
?????? 假設(shè)我們現(xiàn)在擁有一些變動(dòng)的信息,然后需要通過這些信息生成如下格式的 word doc 文件:
?????? 那么根據(jù)上面的描述,首先第一步,我們建立一個(gè)對(duì)應(yīng)格式的 doc 文件作為模板,其內(nèi)容是這樣的:
?????? 有了這樣一個(gè)模板之后,我們就可以建立對(duì)應(yīng)的 HWPFDocument ,然后替換對(duì)應(yīng)的變量為相應(yīng)的值,再把 HWPFDocument 輸出到對(duì)應(yīng)的輸出流即可。下面是對(duì)應(yīng)的代碼。
public class HwpfTest { @Test public void testWrite() throws Exception { String templatePath = "D:\\word\\template.doc"; InputStream is = new FileInputStream(templatePath); HWPFDocument doc = new HWPFDocument(is); Range range = doc.getRange(); //把range范圍內(nèi)的${reportDate}替換為當(dāng)前的日期 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("D:\\word\\write.doc"); //把doc輸出到輸出流中 doc.write(os); this.closeStream(os); this.closeStream(is); } /** * 關(guān)閉輸入流 * @param is */ private void closeStream(InputStream is) { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 關(guān)閉輸出流 * @param os */ private void closeStream(OutputStream os) { if (os != null) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } }
?
(注:本文是基于 poi3.9 所寫)
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
