前言
在对于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/Output)
接下来,我们要撰写程式来读取所要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解析器。
设定输出换行字元
在这里,我们加入一个n 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 ("");
}
利用上列的程式,我们将可以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来减少程式撰写上的困扰。