转;
[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]