java Apache poi 對(duì)word doc文件進(jìn)行讀寫操作
使用POI讀寫Word doc文件
Apache poi的hwpf模塊是專門用來(lái)對(duì)word doc文件進(jìn)行讀寫操作的。在hwpf里面我們使用HWPFDocument來(lái)表示一個(gè)word doc文檔。在HWPFDocument里面有這么幾個(gè)概念:
Range:它表示一個(gè)范圍,這個(gè)范圍可以是整個(gè)文檔,也可以是里面的某一小節(jié)(Section),也可以是某一個(gè)段落(Paragraph),還可以是擁有共同屬性的一段文本(CharacterRun)。
Section:word文檔的一個(gè)小節(jié),一個(gè)word文檔可以由多個(gè)小節(jié)構(gòu)成。
Paragraph:word文檔的一個(gè)段落,一個(gè)小節(jié)可以由多個(gè)段落構(gòu)成。
CharacterRun:具有相同屬性的一段文本,一個(gè)段落可以由多個(gè)CharacterRun組成。
Table:一個(gè)表格。
TableRow:表格對(duì)應(yīng)的行。
TableCell:表格對(duì)應(yīng)的單元格。
Section、Paragraph、CharacterRun和Table都繼承自Range。
1 讀word doc文件
在日常應(yīng)用中,我們從word文件里面讀取信息的情況非常少見,更多的還是把內(nèi)容寫入到word文件中。使用POI從word doc文件讀取數(shù)據(jù)時(shí)主要有兩種方式:通過(guò)WordExtractor讀和通過(guò)HWPFDocument讀。在WordExtractor內(nèi)部進(jìn)行信息讀取時(shí)還是通過(guò)HWPFDocument來(lái)獲取的。
1.1 通過(guò)WordExtractor讀文件
在使用WordExtractor讀文件時(shí)我們只能讀到文件的文本內(nèi)容和基于文檔的一些屬性,至于文檔內(nèi)容的屬性等是無(wú)法讀到的。如果要讀到文檔內(nèi)容的屬性則需要使用HWPFDocument來(lái)讀取了。下面是使用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());
//輸出頁(yè)眉的內(nèi)容
System.out.println("頁(yè)眉:" + extractor.getHeaderText());
//輸出頁(yè)腳的內(nèi)容
System.out.println("頁(yè)腳:" + 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());
//頁(yè)數(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 通過(guò)HWPFDocument讀文件
HWPFDocument是當(dāng)前Word文檔的代表,它的功能比WordExtractor要強(qiáng)。通過(guò)它我們可以讀取文檔中的表格、列表等,還可以對(duì)文檔的內(nèi)容進(jìn)行新增、修改和刪除操作。只是在進(jìn)行完這些新增、修改和刪除后相關(guān)信息是保存在HWPFDocument中的,也就是說(shuō)我們改變的是HWPFDocument,而不是磁盤上的文件。如果要使這些修改生效的話,我們可以調(diào)用HWPFDocument的write方法把修改后的HWPFDocument輸出到指定的輸出流中。這可以是原文件的輸出流,也可以是新文件的輸出流(相當(dāng)于另存為)或其它輸出流。下面是一個(gè)通過(guò)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è)趯慸oc文件的時(shí)候是通過(guò)HWPFDocument來(lái)寫的,而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è)谏蓋ord文件的時(shí)候都是生成某一類文件,該類文件的格式是固定的,只是某些字段不一樣罷了。所以在實(shí)際應(yīng)用中,我們大可不必將整個(gè)word文件的內(nèi)容都通過(guò)HWPFDocument生成。而是先在磁盤上新建一個(gè)word文檔,其內(nèi)容就是我們需要生成的word文件的內(nèi)容,然后把里面一些屬于變量的內(nèi)容使用類似于“${paramName}”這樣的方式代替。這樣我們?cè)诨谀承┬畔⑸蓋ord文件的時(shí)候,只需要獲取基于該word文件的HWPFDocument,然后調(diào)用Range的replaceText()方法把對(duì)應(yīng)的變量替換為對(duì)應(yīng)的值即可,之后再把當(dāng)前的HWPFDocument寫入到新的輸出流中。這種方式在實(shí)際應(yīng)用中用的比較多,因?yàn)樗坏梢詼p少我們的工作量,還可以讓文本的格式更加的清晰。下面我們就來(lái)基于這種方式做一個(gè)示例。
假設(shè)我們現(xiàn)在擁有一些變動(dòng)的信息,然后需要通過(guò)這些信息生成如下格式的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所寫)
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Springboot使用put、delete請(qǐng)求報(bào)錯(cuò)405的處理
這篇文章主要介紹了Springboot使用put、delete請(qǐng)求報(bào)錯(cuò)405的處理方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
web.xml詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章給大家詳細(xì)介紹了web.xml的相關(guān)知識(shí),需要的朋友可以參考下2017-07-07
IntelliJ IDEA查看方法說(shuō)明文檔的圖解
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA查看方法說(shuō)明文檔的圖解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10
Java如何實(shí)現(xiàn)遠(yuǎn)程文件下載到本地目錄
本文介紹了如何使用Java來(lái)實(shí)現(xiàn)遠(yuǎn)程文件的下載功能,主要通過(guò)HTTPS路徑下載文件到本地目錄,詳細(xì)介紹了相關(guān)代碼和測(cè)試步驟,并提供了實(shí)際案例供參考,本文旨在幫助需要實(shí)現(xiàn)文件下載功能的開發(fā)者快速掌握核心技術(shù)2024-10-10
在Spring中如何注入動(dòng)態(tài)代理Bean
這篇文章主要介紹了在Spring中如何注入動(dòng)態(tài)代理Bean問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
Spring?@bean和@component注解區(qū)別
本文主要介紹了Spring?@bean和@component注解區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
FuncGPT慧函數(shù)保護(hù)數(shù)據(jù)安全提高代碼質(zhì)量減少軟件故障(java示例)
這篇文章主要為大家介紹了FuncGPT慧函數(shù)保護(hù)數(shù)據(jù)安全提高代碼質(zhì)量減少軟件故障(java示例),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10

