使用POI讀寫Word
1 讀word doc檔案
1.1 通過WordExtractor讀檔案
1.2 通過HWPFDocument讀檔案
2 寫word doc檔案
Apache poi的hwpf模組是專門用來對word doc檔案進行讀寫操作的。在hwpf裡面我們使用HWPFDocument來表示一個word doc文件。在HWPFDocument裡面有這麼幾個概念:
l Range:它表示一個範圍,這個範圍可以是整個文件,也可以是裡面的某一小節(Section),也可以是某一個段落(Paragraph),還可以是擁有共同屬性的一段文字(CharacterRun)。
l Section:word文件的一個小節,一個word文件可以由多個小節構成。
l Paragraph:word文件的一個段落,一個小節可以由多個段落構成。
l CharacterRun:具有相同屬性的一段文字,一個段落可以由多個CharacterRun組成。
l Table:一個表格。
l TableRow:表格對應的行。
l TableCell:表格對應的單元格。
Section、Paragraph、CharacterRun和Table都繼承自Range。
1 讀word doc檔案
在日常應用中,我們從word檔案裡面讀取資訊的情況非常少見,更多的還是把內容寫入到word檔案中。使用POI從word doc檔案讀取資料時主要有兩種方式:通過WordExtractor
1.1 通過WordExtractor讀檔案
在使用WordExtractor讀檔案時我們只能讀到檔案的文字內容和基於文件的一些屬性,至於文件內容的屬性等是無法讀到的。如果要讀到文件內容的屬性則需要使用HWPFDocument來讀取了。下面是使用WordExtractor讀取檔案的一個示例:
Java程式碼- 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());
- //輸出頁首的內容
- System.out.println("頁首:" + extractor.getHeaderText());
- //輸出頁尾的內容
- System.out.println("頁尾:" + extractor.getFooterText());
- //輸出當前word文件的元資料資訊,包括作者、文件的修改時間等。
- System.out.println(extractor.getMetadataTextExtractor().getText());
- //獲取各個段落的文字
- String paraTexts[] = extractor.getParagraphText();
- for (int i=0; i<paraTexts.length; i++) {
- System.out.println("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) {
- //作者
- System.out.println(info.getAuthor());
- //字元統計
- System.out.println(info.getCharCount());
- //頁數
- System.out.println(info.getPageCount());
- //標題
- 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());
- }
- /**
- * 關閉輸入流
- * @param is
- */
- private void closeStream(InputStream is) {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
1.2 通過HWPFDocument讀檔案
HWPFDocument是當前Word文件的代表,它的功能比WordExtractor要強。通過它我們可以讀取文件中的表格、列表等,還可以對文件的內容進行新增、修改和刪除操作。只是在進行完這些新增、修改和刪除後相關資訊是儲存在HWPFDocument中的,也就是說我們改變的是HWPFDocument,而不是磁碟上的檔案。如果要使這些修改生效的話,我們可以呼叫HWPFDocument的write方法把修改後的HWPFDocument輸出到指定的輸出流中。這可以是原檔案的輸出流,也可以是新檔案的輸出流(相當於另存為)或其它輸出流。下面是一個通過HWPFDocument讀檔案的示例:
Java程式碼- 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();//在記憶體中進行刪除,如果需要儲存到檔案中需要再把它寫回檔案
- //把當前HWPFDocument寫到輸出流中
- doc.write(new FileOutputStream("D:\\test.doc"));
- 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();
- System.out.println("書籤數量:" + 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("結束位置:" + 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);
- //輸出單元格的文字
- 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) {
- //獲取段落數
- 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());
- }
- }
- /**
- * 插入內容到Range,這裡只會寫到記憶體中
- * @param range
- */
- private void insertInfo(Range range) {
- range.insertAfter("Hello");
- }
- }
2 寫word doc檔案
在使用POI寫word doc檔案的時候我們必須要先有一個doc檔案才行,因為我們在寫doc檔案的時候是通過HWPFDocument來寫的,而HWPFDocument是要依附於一個doc檔案的。所以通常的做法是我們先在硬碟上準備好一個內容空白的doc檔案,然後建立一個基於該空白檔案的HWPFDocument。之後我們就可以往HWPFDocument裡面新增內容了,然後再把它寫入到另外一個doc檔案中,這樣就相當於我們使用POI生成了word doc檔案。
在實際應用中,我們在生成word檔案的時候都是生成某一類檔案,該類檔案的格式是固定的,只是某些欄位不一樣罷了。所以在實際應用中,我們大可不必將整個word檔案的內容都通過HWPFDocument生成。而是先在磁碟上新建一個word文件,其內容就是我們需要生成的word檔案的內容,然後把裡面一些屬於變數的內容使用類似於“${paramName}”這樣的方式代替。這樣我們在基於某些資訊生成word檔案的時候,只需要獲取基於該word檔案的HWPFDocument,然後呼叫Range的replaceText()方法把對應的變數替換為對應的值即可,之後再把當前的HWPFDocument寫入到新的輸出流中。這種方式在實際應用中用的比較多,因為它不但可以減少我們的工作量,還可以讓文字的格式更加的清晰。下面我們就來基於這種方式做一個示例。
假設我們現在擁有一些變動的資訊,然後需要通過這些資訊生成如下格式的word doc檔案:
那麼根據上面的描述,首先第一步,我們建立一個對應格式的doc檔案作為模板,其內容是這樣的:
有了這樣一個模板之後,我們就可以建立對應的HWPFDocument,然後替換對應的變數為相應的值,再把HWPFDocument輸出到對應的輸出流即可。下面是對應的程式碼。
Java程式碼- 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範圍內的${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("D:\\word\\write.doc");
- //把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();
- }
- }
- }
- }
(注:本文是基於poi3.9所寫)
原文轉載自:http://haohaoxuexi.iteye.com/blog/2031335