2007年6月17日 星期日

利用Java切割XML

XML檔案很方便。一方面易讀,不需要特殊軟體就可得知內容;一方面支援性佳,大部分的程式語言都有解析XML檔的程式庫。但它也有缺點,當檔案太大時,不容易找出特定的資訊。這裡的不容易有兩個解釋:一個是特定資訊的比對,一個是速度。資料的比對我門可以用XPath或XQuery找尋特定節點及內容,但還是有速度太慢的問題。我最近所遇到的 XML檔大小都介於1M至5M之間,一次都要搜尋兩三百個這種檔案,如果每個都要用XPath去找尋特定節點,是節點數量而定,大概都要花上數分鐘至數十分鐘。所以在處理這種狀況時,我運用了兩種方法,一種是建立索引,將所要找的內容與XML檔案之間的連結儲存,就類似Java中的Map;另外為了減輕XPath計算的負擔,將XML切割成許多小檔案,當然,Map之間的連結也都連結至這些小檔案。

彷間的Java書籍及網路好像甚少提及此部分,大多是用DOM及SAX讀取XML檔,並利用這些model來讀取節點。以下面這個例子來看,


<books>
<book id="1">
<author>Mike</author>
<title>Guess who</title>
<description>
This is a book about a man who never ......
</description>
</book>
<book id="2">
<author>John</author>
<title>Guess where</title>
<description>
This is a book about a place where no ......
</description>
</book>
</books>


如果我要將兩個book節點分別存成檔案該如何做呢?一般用DOM來取得book節點後,由於其下無內容,就只能讀取到它的屬性,至於author、title及description都只能再利用getChildNodes去取得,如此就只能自己去拼湊這個XML檔了,當XML結構更複雜時,這任務就更艱難了。

感謝w3c.org提供的XML Serializer可將DOM序列化成XML字串。 下面的範例程式可將上述檔案切出兩個book節點及其內容:


import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.ls.*; // 就是它
import java.io.*;

public class TestXMLSerializer {
public static void main(String[] args) {
try {
File f = new File("test.xml");

// ------ 取得XML Document ----------
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
Document doc = docBuilder.parse(f);

// -------- 建立DOM Serializer ---------
DOMImplementationLS lsimpl = (DOMImplementationLS) doc.getImplementation();
org.w3c.dom.ls.LSSerializer ls = lsimpl.createLSSerializer();
ls.getDomConfig().setParameter("xml-declaration", false);

//---------選擇欲擷取內容的節點----------
NodeList bookNodeList = doc.getElementsByTagName("book");
if (bookNodeList.getLength()>0) {
for (int i=0;i<bookNodeList.getLength();i++) {
Node bookNode = bookNodeList.item(i);

// ------這樣就可以擷取book節點下的所有內容了-------------
String bookContent = ls.writeToString(bookNode);
System.out.println(bookContent + "\n");
}
} else {
System.out.println("There is no tag named 'book'");
}
}
catch (Exception ex)
{
System.out.println(ex.getMessage());
}
}
}


當然,切割完的部分如果不符合XML規則,例如沒有單一root,就要自行加上,如此就可以將XML切割成好幾個小XML檔了。