InfoQ: 用Java操作Office 2007
原文地址:http://blog.csdn.net/smartcat86/article/details/1854932
 
用Java操作Office 2007
作者?Ted Neward譯者?張立?發布于 2007年9月20日 上午12時41分
在上一篇“Office富客戶端應用”中,我們提到了將Office 2007平臺作為一個構建富客戶端應用程序的基本平臺,并通過不同的手段使用Java來進行互操作。 但是,有一個Office/Java互操作的方面沒有考慮到,那就是使Office和Java共同工作,也就是說讓Java應用程序來操作Office文檔:比如創建文檔,編輯文檔,收集數據等等。
從以往看來,這其中經常會出現一些問題,這是由于Office文檔(主要是Word,Excel和PowerPoint)是存儲在一個二進制格式文件中,在COM中被稱為結構化存儲格式, 是一個通過COM接口的層次化二進制格式。 對COM開發者(或者其他使用COM相關語言的開發者,如Visual Basic, Delphi 和C++/ATL)而言非常方便,但產生的文件對于那些不能“講COM”的語言是無法訪問的。有許許多多的應用程序都是為了讓Java語言可以訪問這些文件的內容;比如大家都知道Excel可以讀取逗號分隔符文件(CSV),因此,Java應用程序相應將數據導出到Excel友好的格式時一般會選用CSV格式(或是其他丑陋的格式)。Word則是可以讀取富文本格式(RTF)文件,而RTF標準是公開和有詳細文檔的。Office的后來者,Office 2003,引入了一個新的XML格式(WordML),Java開發者可以用它來讀寫Office文檔,但是這些格式并沒有很好的文檔,Java開發者頻繁的發現自己是通過試錯法來進行WordML格式的學習。 各種各樣的開源項目都參與進來想要解決這個問題,比如Apache的POI框架,可以用來讀寫Excel文檔,還有各種各樣的Java-COM解決方案,這些解決方案一般傾向于使用和Office自己使用的結構化存儲應用程序接口相同的應用程序接口進行Excel文檔的讀寫,但很難滿足需要,直到現在,開發者不得不指出Office文檔格式的內部結構是一個非常復雜的結構,另外一點毋庸置疑的是它是一個沒有完整文檔的結構。
總體上來說,如果溫和一點說的話,Java/Office的故事是一個非常討厭的境況。對于Java的開發人員而言,他們要么一邊嘴里說著“Office這種破東西怎么還會有人想去用它”一邊用記憶里的伊索寓言來安慰自己,要么干脆告訴那些使用Office的客戶由于Microsoft和Sun兩家公司之間的訴訟,Java不能操作Office。
對于Office 2007來說,微軟毫無疑問的邁出了解決這些問題的一大步。沒有比原始的JDK更復雜的東西---也就是說并不要求使用一些第三方的庫---Java應用程序現在可以讀寫任何Office 2007的文檔,這是由于Office 2007文檔現在使用的是XML文檔的ZIP格式文件。 這種格式被稱作“OpenMXL”規范并且已經被提交到歐洲計算機制造商協會(ECMA),這個協會同樣擁有C#語言和CLI運行時規范,所有的OpenXML規范現在都可以被任何人自由的從ECMA的網站下載。 除了這些,再安裝好Office 2007(為了驗證和作一些測試)和一個標準的Java6 JDK安裝,Java現在可以打開任何的Office 2007文檔,找出來文檔中間的內容,操作它們,并且再次保存這些數據。
與上篇文章不同,在這篇文章中,除了創建一個簡單的應用程序之外,代碼將會使用一種首先由Stuart Halloway提出的、被稱作探索測試(exploration testing)的技術。在一個探索測試中,開發者編寫單元測試用來探索應用程序接口,使用單元測試世界中的斷言驗證結果的正確性。探索測試帶來的好處是當一個新版本的應用程序接口可用時---在這個例子中,可能是一個新版本的Office---運行這些測試可以用來確認新版本的采用不會影響到原本對應用程序接口的使用。對于初學者來說,讓我們首先快速的了解一下Office 2007文檔。首先看一個僅僅包含文本的Word 2007文檔,就像下面一樣:
當保存的時候,使用Word 2007將它保存為“Hello.docx”,除非你使用了向后兼容格式,比如說Office 2003的WordML格式,或者是更老的Word 97二進制結構化存儲格式。“.docx”文件是OpenXML格式的,微軟的文檔中聲稱該格式是XML文檔的ZIP壓縮格式文件,這些文件中包含了文檔中的數據和格式,存儲的方式與之前的Office版本中的二進制結構化存儲應用程序接口存儲數據的方式有些類似。如果這是真的,那么使用Java中提供的用來處理ZIP和TAR格式的“jar”實用工具應該可以展示這些內容,而事實上,它的確可以:
Word 2007文檔的基本格式已經非常明顯了,僅僅通過控制臺的輸出就可以看到。(事實上,“jar”實用工具所展示的這激動人心的一切,說明java.util.jar和/或 java.util.zip包同樣可以簡單的訪問這些內容。)幾乎沒有對規范作任何的破解,很明顯,文檔中的主要內容應該被存儲到了“document.xml”文件中,剩余的其他XML文件則應該是各種各樣的輔助部分,比如文檔中應用到的字體(fontTable.xml)和使用到的Office主題(theme/theme1.xml),等等。
是時間來編寫一些探索測試了。(我們鼓勵感興趣的讀者打開一個文本編輯器或者集成開發環境,并將下面的內容填入你的JUnit 4測試類當中,并且擴展這些測試。) 使用JUnit 4,第一個測試是為了簡單的確認文件在我們預想的位置(顯然這是下面測試可以運行的一個必要的需求)。
@Test public void verifyFileIsThere() {assertTrue(new File("hello.docx").exists());
assertTrue(new File("hello.docx").canRead());
assertTrue(new File("hello.docx").canWrite());
}
下面的測試簡單的驗證了我們可以使用Java庫中的java.util.zip.ZipFile來打開這個文件:
@Test public void openFile()throws IOException, ZipException
{
ZipFile docxFile =
new ZipFile(new File("hello.docx"));
assertEquals(docxFile.getName(), "hello.docx");
}
現在一切看來都非常不錯。Java的ZipFile類正確的識別了我們的文件,一個zip文件,如果我們還能繼續保持這樣的運氣,讓我們繼續我們的測試,來遍歷一下,識別文檔中的內容并找出其中的數據。讓我們編寫一個快速的測試來從“document.xml”文件中找出所有的內容。
@Test public void listContents()throws IOException, ZipException
{
boolean documentFound = false;
ZipFile docxFile =?
new ZipFile(new File("hello.docx"));
Enumeration entriesIter =
docxFile.entries();
while (entriesIter.hasMoreElements())
{
ZipEntry entry = entriesIter.nextElement();
if (entry.getName().equals("document.xml"))
documentFound = true;
}
assertTrue(documentFound);
}
令人詫異的是,當我們運行測試的時候,測試過程產生了一個失敗;并沒有找到“document.xml”文件,這是由于ZipFile/ZipEntry 應用程序接口需要壓縮文件中完整的路徑名稱。將測試中的路徑改為“word/document.xml”,測試就通過了。
很好,我們已經找到文件了,下面讓我們打開這個文件看看XML里面是什么。這非常簡單,因為ZipFile有一個返回ZipEntry的應用程序接口。
@Test public void getDocument()throws IOException, ZipException
{
ZipFile docxFile =?
new ZipFile(new File("hello.docx"));
ZipEntry documentXML =
docxFile.getEntry("word/document.xml");
assertNotNull(documentXML);
}
ZipFile代碼可以返回它包含的實體內容,通過調用getInputStream()方法即可,不要對InputStream產生任何懷疑。將InputStream發送到一個DOM節點中就可以創建一個關于該文檔的DOM。
@Test public void fromDocumentIntoDOM()throws IOException, ZipException, SAXException,
ParserConfigurationException
{
ZipFile docxFile =
new ZipFile(new File("hello.docx"));
ZipEntry documentXML =
docxFile.getEntry("word/document.xml");
InputStream documentXMLIS =
docxFile.getInputStream(documentXML);
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance();
Document doc =
dbf.newDocumentBuilder().parse(documentXMLIS);
assertEquals("[w:document: null]",
doc.getDocumentElement().toString());
}
事實上,與其他支持各種Word所需格式的XML文檔相比,document.xml文件的內容(為了明顯起見,將命名空間聲明等內容去除)看起來也相當乏味:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document ...>
<w:body>
<w:p w:rsidR="00DE36E5" w:rsidRDefault="00DE36E5">
<w:r>
<w:t>Hello, from Office 2007!</w:t>
</w:r>
</w:p>
<w:sectPr w:rsidR="00DE36E5">
<w:pgSz w:w="12240" w:h="15840"/>
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/>
<w:cols w:space="720"/>
<w:docGrid w:linePitch="360"/>
</w:sectPr>
</w:body>
</w:document>
關于文檔中各個元素具體代表什么內容的細節已經超出了這篇文章的討論范圍,讀者可以查閱OpenXML文檔的具體內容來獲得參考,但是文檔中的主要內容是十分明顯的。比如說文檔中包括“p”元素(段落),包括“r”元素(文本區),包括“t”元素(文本),在本例的hello.docx文檔中,單句“Hello from Office 2007”就是由這些元素構成的。
讀過文件的內容后,現在可以來修改這些內容了,將其寫到文件中,并用Word 2007打開它。快速的查看ZipFile和ZipEntry的應用程序接口可以發現這樣一個問題:盡管這些類可以用來讀取一個zip文件,但它們并不能寫入或創建它們。
有很多可用的方法可以用于解決這個問題。一個簡單的方法是將XML文件的內容文本寫到一個字符串中,并將這個字符串存儲到document.xml文件中,然后重新使用ZipOutStream類壓縮所有的內容。另一個方法是使用一些可以編輯zip文件內容的第三方工具(或創建一個),但這些已經脫離了JDK的基本內容,所以在這篇文章中我們將使用ZipOutStream方法。
為了達到我們的目的,我們需要做很多事情。首先,Java應用程序必須定位到DOM的層次結構中,找到“t”節點,然后將它的文本內容替換為我們要寫入到Word文檔中的內容。(“Hello,Office 2007,from Java6!”是個不錯的選擇)產生的新DOM實例必須要保存到磁盤中,使用Java XML 應用程序接口時這并不是一個簡單的任務。(簡單的說來,開發者需要從javax.xml.transform包中創建一個Transformer,然后將XML轉換為一個StreamResult,再交由ByteArrayOutputStream處理。)
一旦上面這些事情都處理完畢后,代碼必須要產生一個ZIP格式的文件,是時候使用ZipOutputStream了,但由于只需要改變文檔的內容,而不需要改變它的樣式、字體以及格式,其他的部分可以從原始的文件中拷貝過來。使用一個簡單的循環,遍歷原始文件中的ZipEntries中所有的內容(除了word/document.xml,該文件中的內容需要被改變)并將其導出到一個新的ZipEntry中并寫入該實體就足夠了。當所有的工作都完成后,代碼將會是以下的樣子:
@Test public void modifyDocumentAndSave()throws IOException, ZipException, SAXException,
ParserConfigurationException,
TransformerException,
TransformerConfigurationException
{
ZipFile docxFile =
new ZipFile(new File("hello.docx"));
ZipEntry documentXML =
docxFile.getEntry("word/document.xml");
InputStream documentXMLIS =
docxFile.getInputStream(documentXML);
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance();
Document doc =
dbf.newDocumentBuilder().parse(documentXMLIS);
Element docElement = doc.getDocumentElement();
assertEquals("w:document", docElement.getTagName());
Element bodyElement = (Element)
docElement.getElementsByTagName("w:body").item(0);
assertEquals("w:body", bodyElement.getTagName());
Element pElement = (Element)
bodyElement.getElementsByTagName("w:p").item(0);
assertEquals("w:p", pElement.getTagName());
Element rElement = (Element)
pElement.getElementsByTagName("w:r").item(0);
assertEquals("w:r", rElement.getTagName());
Element tElement = (Element)
rElement.getElementsByTagName("w:t").item(0);
assertEquals("w:t", tElement.getTagName());
assertEquals("Hello, from Office 2007!",
tElement.getTextContent());
tElement.setTextContent(
"Hello, Office 2007, from Java6!");
Transformer t =
TransformerFactory.newInstance().newTransformer();
ByteArrayOutputStream baos =
new ByteArrayOutputStream();
t.transform(new DOMSource(doc),
new StreamResult(baos));
ZipOutputStream docxOutFile = new ZipOutputStream(
new FileOutputStream("response.docx"));
Enumeration entriesIter =
docxFile.entries();
while (entriesIter.hasMoreElements())
{
ZipEntry entry = entriesIter.nextElement();
if (entry.getName().equals("word/document.xml"))
{
byte[] data = baos.toByteArray();
docxOutFile.putNextEntry(
new ZipEntry(entry.getName()));
docxOutFile.write(data, 0, data.length);
docxOutFile.closeEntry();
}
else
{
InputStream incoming =
docxFile.getInputStream(entry);
byte[] data = new byte[1024 * 16];
int readCount =
incoming.read(data, 0, data.length);
docxOutFile.putNextEntry(
new ZipEntry(entry.getName()));
docxOutFile.write(data, 0, readCount);
docxOutFile.closeEntry();
}
}
docxOutFile.close();
}
很抱歉這里展示了這么多代碼,但是說實在的,這也是Java相比其他語言或者庫的一個弱點。幸運的是我們的努力得到了以下的回報:
顯然我們可以作很多事情來改善上面的場景。
首先,一個更好的XML操作庫,可以更好的支持XPath技術,能夠原生的序列化XML DOM結構到磁盤的庫會對減少大量的代碼有所幫助。JDOM,一個開源的Java/XML庫(可以在jdom.org中找到),是一個可用的選擇。Apache的XMLBeans也不錯。一個必然的結果是我們可以獲得更好的描述OpenXML格式的模式文檔,并使用它們來產生一系列的Java類來更好的反映OpenXML文檔的格式。開發者則可以更好的使用原生的Java類工作,而不是通過“Document”類和“Element”類。
其次,這些方法可以被綁定到一個更加針對Office的應用程序接口當中,可以改善針對實際存儲的Word(或是Excel,PowerPoint)文檔的XML文件操作的抽象層,關注那些擁有段落,字體等等其他的文檔。實質上,像POI那樣的庫應該可以通過更新類反映Office XML格式的改動,理想的話,可以同時支持寫入二進制結構化存儲格式和新的OpenXML格式。
再次,Java可以對其ZIP文件格式的支持進行一些改動,同樣,這樣的目的也可以由使用一些第三方的庫來完成。
盡管使用了一些笨重的應用程序接口調用,但是當想到Office平臺對Java開發人員有多開放時還是非常的令人激動和振奮。在Java和Office應用程序的互操作性上,在Java應用程序中使用Office,還有在Java中創建和讀寫Office文件格式上,Office平臺對Java社區的開發人員比以往任何時候都更加開放了。
本文附帶的示例代碼可以在此處下載。
查看英文原文:?譯者簡介:張立,博士研究生,喜歡新技術,新思想。經歷了一些企業級軟件開發后,逐漸將興趣轉向C#和JAVA的企業級應用。同時對動態語言的發展非常關注,喜歡用Python進行一些計算,對Ruby也傾注了一定的精力。大部分時間在學校從事一些理論研究,工作之余關注開源軟件的進展。總結
以上是生活随笔為你收集整理的InfoQ: 用Java操作Office 2007的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 电脑相机,万能相机电脑版
- 下一篇: Ubuntu 安装小狼豪输入法
