前言
在對於 Sun Microsystems Inc. Java API for XML Parsing (JAXP) 程式開發套件所包含的SAX及DOM APIs 函式庫做過簡略的介紹之後,在接下來的這個章節中,筆者將針對使用event-driven及serial-access機制來存取XML架構文件的SAX做Java程式撰寫示範及介紹。
建造 Skeleton
在程式撰寫一開始,我們先建立一個命名為Echo.java的Java程式檔案,然後在這個Java檔案中撰寫應用程式的基本架構(Skeleton):
public class Echo extends HandlerBase
{
public static void main (String argv[])
{
...
}
}
這一個我們所建立的class並延伸了HandlerBase。 HandlerBase主要的功能為提供我們在之前的章節 "Application Programming Interface (API) 概觀"中提到的所有界面,讓我們可以在程式撰寫時應用。
包含所需的 Classes
在建立了Java的程式基礎架構Skeleton後,我們接著必需在這個Skeleton之前使用import指令來加入其它我們會使用到的Java classes:
import java.io.*;
import org.xml.sax.*;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
public class Echo extends HandlerBase
{
...
第一行java.io中的classes主要為用在做資料的輸入及輸出。 然後,org.xml.sax package定義所有的界面讓我們可以使用SAX解析器。 SAXParserFactory class主要為用來產生SAX 解析器, 如果它無法產生一個解析器匹配指定的選項,它將會throws一個ParserConfigurationException。 最後, SAXParser將傳回factory的物件。
設定 I/O (Input/Ouput)
接下來,我們要撰寫程式來讀取所要echo的XML檔案名稱以及設定Output Stream。
public static void main (String argv [])
{
if (argv.length != 1) {
System.err.println ("Usage: cmd filename");
System.exit (1);
}
try {
// Set up output stream
out = new OutputStreamWriter (System.out, "BIG5");
} catch (Throwable t) {
t.printStackTrace ();
}
System.exit (0);
}
static private Writer out;
當我們在創立OutputStreamWriter時,我們選擇使用BIG5的字元編碼。 並且我們也可以選擇使用Java平台支援的其他字元編碼,例如: US-ASCII、UTF-8或UTF-16。
設定解析器
完成了上面的步驟之後,再來我們可以將以下粗體字的部份加入之前所寫的程式中來開始設定解析器。
public static void main (String argv [])
{
if (argv.length != 1) {
System.err.println ("Usage: cmd filename");
System.exit (1);
}
// Use the default (non-validating) parser
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
// Set up output stream
out = new OutputStreamWriter (System.out, "BIG5");
// Parse the input
SAXParser saxParser = factory.newSAXParser();
saxParser.parse( new File(argv [0]), new Echo() );
} catch (Throwable t) {
t.printStackTrace ();
}
System.exit (0);
}
使用這幾行程式碼,我們利用SAXParserFactory製造了一個factory物件實體。 然後由factory 得到一個SAX解析器並且給予這個解析器一個class實體來處理解析的事件,告知要對那一個輸入的檔案做處理。
執行 DocumentHandler 界面
DocumentHandler界面需要許多不同的method,讓SAX解析器依照不同的解析事件給與不同的回應。 現在,我們只需要利用到其中的五個methods: startDocument、endDocument、startElement、endElement以及 characters。 我們將下列粗體字部份加到先前已完成的程式中,設定method來處理這些事件:
...
static private Writer out;
public void startDocument () throws SAXException
{
}
public void endDocument () throws SAXException
{
}
public void startElement (String name, AttributeList attrs) throws SAXException
{
}
public void endElement (String name) throws SAXException
{
}
public void characters (char buf [], int offset, int len) throws SAXException
{
}
...
DocumentHandler界面需要以上程式的每一個method產生錯誤時throw SAXException。 一個從這裡 throw的exception將會先被送回到解析器,然後解析器將傳回exception到呼叫解析器的程式碼之上來做處理。
在讀取XML文件內容的過程裡,當讀取到一個啟始標籤或結尾標籤時,這個標籤的名稱將被當做一個字串傳至startElement或endElement method。 當讀取到的為一個啟始標籤時,這個標籤中所定義的任何屬性也將被包含在AttributeList之中傳遞。 在元素(element)中所包含的字元將會以字元陣列型態,連同字元的數目(長度)及一個標示字元陣列內第一個字元位置的offset 一起傳輸。
撰寫輸出
由於DocumentHandler methods錯誤產生時throw的為SAXExceptions,它並沒有定義throw IOExceptions 來處理在資料輸出時可能產生的錯誤。 所以在這裡我們可以利用SAXException來包裝另外一個 IOExceptions。
public void characters (char buf [], int offset, int Len) throws SAXException
{
}
private void emit (String s) throws SAXException
{
try {
out.write (s);
out.flush ();
} catch (IOException e) {
throw new SAXException ("I/O error", e);
}
}
...
emit method的功能為用來做字串輸出的處理。 當emit method被呼叫的時候,任何的輸入/輸出(I/O)錯誤都將在SAXException中連同一個識別錯誤的訊息一起被包裝送出。 這個exception錯誤然後將被傳回到SAX解析器。
設定輸出換行字元
在這裡,我們加入一個nl method用來在資料輸出行的結尾產生一個使用者目前所使用系統環境的換行字元(next line)。
private void emit (String s)
{
...
}
private void nl () throws SAXException
{
String lineEnd = System.getProperty("line.separator");
try {
out.write (lineEnd);
} catch (IOException e) {
throw new SAXException ("I/O error", e);
}
}
處理文件事件(Document Event)
最後,我們撰寫DocumentHandler事件程式碼來實際執行我們之前所增加的method。 增加下面程式中粗體字部份來處理startDocument和endDocument事件:
public void startDocument () throws SAXException
{
emit ("<?xml version='1.0' encoding='BIG5'?>");
nl();
}
public void endDocument () throws SAXException
{
try {
nl();
out.flush ();
} catch (IOException e) {
throw new SAXException ("I/O error", e);
}
}
在這個程式中,當解析器遇到文件的啟始時,程式將會自動echo XML的宣告。 遇到文件的結尾時,我們只須加入一個最後的換行字元(newline),然後輸出output stream。
在加入startDocument和endDocument事件處理程式後,我們然後增加下面程式中粗體字部份來處理startElement和endElement事件:
public void startElement (String name, AttributeList attrs) throws SAXException
{
emit ("<"+name);
if (attrs != null) {
for (int i = 0; i < attrs.getLength (); i++) {
emit (" ");
emit (attrs.getName(i)+"=\""+attrs.getValue (i)+"\"");
}
}
emit (">");
}
public void endElement (String name) throws SAXException
{
emit ("</"+name+">");
}
利用上列的程式,我們將可以echo所有XML文件中的標籤(tag),並顯示所有包含在啟始標籤中的屬性(attribute)。 最後,我們需要加入下列程式在characters method中來echo解析器所讀取的文字。
public void characters (char buf [], int offset, int len) throws SAXException
{
String s = new String(buf, offset, len);
emit (s);
}
直到這裡,我們已經完成了一個基本的SAX解析器應用程式。 再來我們就可以使用Java編譯器javac來編譯我們所寫的程式然後執行它。
XML測試檔案
在成功的編譯完成我們所寫的Echo.java程式後,我們將需要一個XML檔案來測試我們所撰寫的解析程式。 在這裡筆者將使用在上一個章節"Java對MXL程式支援(三) 建立一個自己的XML文件"中我們所建立的XML檔案sample.xml來做為測試範例。
sample.xml的內容如下:
<?xml version='1.0' encoding='Big5'?>
<!-- XML投影片簡報格式示範 -->
<投影片簡報 標題="簡短的投影片簡報" 日期="簡報日期" 講師="王泳泰">
<!-- 投影片 標題 -->
<投影片 型態="全部">
<標題>XML應用</標題>
</投影片>
<!-- 投影片 概觀 -->
<投影片 型態="全部">
<標題>概觀</標題>
<項目>何謂 <em>XML</em>? </項目>
<項目/>
<項目><em>產業</em>XML化</項目>
</投影片>
</投影片簡報>
解析結果
當我們執行Echo程式並給與sample.xml檔案後,如果我們這一份所給與的sample.xml文件內容為一個well-formed的XML架構文件,我們將會得到 (圖一) 的正確解析輸出結果。
《圖一 well-formed的XML架構文件輸出結果》 |
|
反之,如果我們所給與的XML文件並不是一份well-formed的文件時,例如: 筆者將sample.xml中第十六行的空標符號 "/" 拿掉,我們將會得到如 (圖二) 的輸出結果。
《圖二 not well-formed的XML架構文件輸出結果》 |
|
結論
到這個章節裡,筆者簡單的介紹了如何使用Java程式語言來撰寫一個基本的SAX 解析器,以解析XML文件。 在這個Echo.java示範程式裡,我們看到了methods startDocument、endDocument、startElement、endElement及character的應用方式。 由於SAX的處理效率快速和記憶體需求低,因此SAX被廣泛使用在servlets及network-oriented應用程式中解析處理XML架構文件。 但是由於SAX協定比DOM (Document Object Model)需要更多的程式規劃設計,並且SAX是一個事件驅動的模型(event-driven model),所以在程式形象化(Visualize)上它也比較困難。 因此在開發顯示或修改XML文件的user-oriented應用程式時,筆者建議各位可以使用DOM來減少程式撰寫上的困擾。