转;
[CODE]
SAX只是一个编程接口,是与具体语言和具体平台无关的。所以要利用它来进行编程的话,必须有一个在某种编程语言下实现SAX解析器。这儿,我们谈的是如何在Java下进行XML编程,因而我们需要有实现了SAX接口的类库来完成这个工作。在Java下实现的SAX类库有很多,许多的大公司如Sun,IBM,Orcal等等都有这样的产品。这儿我们主要介绍的是Sun的JAXP类库。JAXP中不仅仅包含了SAX,也有DOM的实现,在这儿我们先要介绍的是关于SAX的部分。
当然,JAXP现在还不是Java核心包的一部分,但以后就不定了喔。毕竟Java还是Sun主推得产品啊,随着XML的日益流行,在某一天JAXP成为Java核心也不是不可能的。但现在,我们需要先下载JAXP类库,它可以在java.sun.com免费下载,然后把它们添加到CLASSPATH变量中去。
SAX包的结构
我们先来看看SAX包的结构以及他们的组成类。和SAX有关的包有四个,分别是:
org.xml.sax:其中定义了SAX的接口。每个公司的具体的SAX实现应该用其公司的标志名称来代替前面的org.xml,比如Sun的SAX解析器包名就是com.sun.xml。此包中还定义了HandlerBase类。用来进行缺省的事件处理,在编程的时候一般都要继承这个类来实现自己的事件处理器类。包中另外的一个类是InputSource,用来输入XML文档。
org.xml.sax.helpers:定义了PaserFactory类,用来生成一个Parser类的实例来解析XML文档。这个类中的makeParser()方法根据环境变量org.xml.sax.paser来建立不同厂家生产的不同的SAX解析器,当然也可以直接在方法中指定解析器的完整类名。
javax.xml.pasers:定义了SAXParserFactory类,用来生成一个SAXPaser类的实例来解析XML文档。SAXParser类是Parser类的一个的包装器,较之Parser类提供了更多更为便捷的方法。
com.sun.xml.parser:这个包才是真正的JAXP SAX解析器类所在。包含有两个SAX解析器com.sun.xml.parser.Parser和com.xun.xml.parser.ValidatingParser用来分别完成non-validating和validating两种解析过程。在调用ParserFactory的makeParser()方法时,需要把这两个类的完整类名(就是上边给出的两个字符串)传递给它。若没有给出,则会使用环境变量org.xml.sax.parser中指定了解析器类。如果使用的是SAXParserFactory的话,使用的环境变量是javax.xml.parsers.SAXParserFactory
SAX处理过程
而SAX编程的过程也不复杂,简单说来可以概叙如下:
1. 用一个ParserFactory的makeParser()方法来建立一个Parser。或者也可以用SAXParserFactory的newSAXParser()方法。
2. 为这个Parser注册一个DocumentHandler来处理事件,通常这是一个HandlerBase的子类。
3. 用一个InputSource来读入一个XML文档。
4. 调用Parser类的
5. 编写DocumentHandler接口的事件处理函数,来处理各个由Parser生成的事件。
HandlerBase是一个实现了DocumentHandler接口的类,它缺省实现了所有的事件方法,当然这些事件方法什么都没有作。通过继承HandlerBase,覆盖想要处理的事件方法,就可以可到一个DocumentHandler了。当然,直接定义一个实现了DocumentHandler接口的类也是可行的,不过就需要实现接口中定义的每一个事件方法,即使你并不需要处理这些事件,这就多多少少有一些麻烦了。
例说SAX
下面我们来看一看具体的代码。首先我们要建立一个测试用的XML文件,如下:
<?xml version=&single;1.0&single;>
<slideshow title="Sample Slide Show" date="Date of publication"
author="Yours Truly">
<slide type="all"><title>Wake up to WonderWidgets!</title></slide>
<slide type="all">
<title>Overview</title>
<item>Why <em>WonderWidgets</em> are great</item>
<item/>
<item>Who <em>buys</em> WonderWidgets</item>
</slide></slideshow>
把它取名为Example1.xml。
顺着提到的上面的顺序,我们来编写一个简单的程序。
首先,当然需要引入需要的包了:
import java.io.*;
import org.xml.sax.*;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
然后编写一个继承自HandleBase的类:
public class ExamApp extends HandlerBase { ...
接着,需要创建一个Parser来解析文档,并注册事件处理器。这段的代码应该是这样的:
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
saxParser.parse( new File(argv [0]), new ExamApp() );
SAXParser的parser方法接受两个参数,一个是要解析的XML文件对象(在程序的第一个参数中给出),另外的一个参数就是实现了DocumentHandler接口的事件处理器类,在这儿就是ExamApp类本身。显然,这一段初始化的代码应该放在ExamApp类的main方法内,以便在程序一开始执行的时候就能够完成相应的工作。
在编写事件函数之前,因为我们的目标是能够把XML文档的内容在显示出来,因而我们要对输入输出着一些设定,这些设定也是在main方法中完成的:
public static void main (String argv [])
{
if (argv.length != 1) {
System.err.println ("Usage: cmd filename");
System.exit (1);
}
try {
// 设定输出流
out = new OutputStreamWriter (System.out, "UTF8");
} catch (Throwable t) {
t.printStackTrace ();
}
System.exit (0);
}
static private Writer out;
这儿当我们设定输出流的时候,选择使用的是UTF-8编码。当然,我们还可以用US-ASCII或者UTF-16,这都是Java平台所支持的。
DocumentHandler的所有方法都能够抛出SAXException例外,但是并不能够处理IOException例外,而这些很在输出的时候是很有可能发生的,因而我们要对输出作一些处理,我们引进了两个函数,分别是emit()和nl():
private void emit (String s)
throws SAXException
{
try {
out.write (s);
out.flush ();
} catch (IOException e) {
throw new SAXException ("I/O error", e);
}
}
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);
}
}
emit()用来输出,并且能够处理相应的IOException,并把它们统一用SAXException来表示。这样,外层的函数就只用处理SAXException,而不必理会可能的IOException了。nl()只是用来输入一个行分隔符。 下面的部分就是要是现的DocumentHandler接口的各个方法了,这儿只是简单的显示标签的名字和内容:
public void startDocument ()
throws SAXException
{
emit ("<?xml version=&single;1.0&single; encoding=&single;UTF-8&single;?>");
nl();
}
public void endDocument ()
throws SAXException
{
nl();
out.flush ();
}
当读到文档的头部时,会触发startDocument()方法,而当文档结束时,会触发endDocument()方法。因为我们这个程序的目的只是简单的输出这个文档的内容,因而,我们在startDocument()方法中,只是简单的输出一个XML文档头,而在endDocument()方法中,输出一个行结束符,然后调用flush ()方法把缓冲中的内容输出。
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+">");
}
当遇到一个元素的开始时,会触发startElement方法,我们可以通过参数得到元素得名称和所有的属性,对属性对象,我们也可以通过getName()和getValue()来获得属性的名称和值。
最后要讲的一个方法是characters(),他也是定义在DocumentHandler()接口中的方法。在读入XML文档的时候,当遇到有包含在元素中的字符串时,会触发characters()方法,并能够通过参数提取这个字符串的内容。
public void characters (char buf [], int offset, int len)
throws SAXException
{
String s = new String(buf, offset, len);
emit (s);
}
我们可以看到characters()提供的存取办法实际上是很弱的,因为它并不不能够得到字符串所属的具体元素的名称。只是在这样的一个简单的应用中,我们并不需要所属元素的名称,而实际上几乎所有有意义的应用,都会需要的。这就需要我们通过其它的方法,比如类全局变量来解决这个问题。当遇到一个元素的开始时,在全局变量中保存一个记录,然后在characters()方法中更具这个记录来得到具体的上下文。这比较的麻烦,特别是对于那些有元素和字符串混杂使用的文档。但对于SAX而言,似乎没有更好的办法了。
[/CODE]