一、本教程旨在为希望运用SAX的Java程序员提供一份快速入门的教程。
二、基本的环境
Sax是一个用于各种各样的XML解析器(或者其他可以看做XML解析器的解析器)通用接口的实现,就像JDBC作为一个能供许多关系型
数据库的通用驱动的一样。如果你想用SAX,你需用如下的几样东西:
a. Java 1.1或更高版本
b. 一个能兼用SAX2(总共有这么几个版本:SAX2M,2001-11-12; SAX 2.0.1, 2002-1-29, SAX2.0.2, 2004-4-24)的XML解析器被安装
在你的Java classpath中
c. SAX2被安装在你的java classpath中(貌似Jdk已经集成了吧?)
大多数的Java/XML工具包括SAX2 和 XML解析器都用到SAX。大多数的web应用服务器也把它最为XML support的核心。特别强调的是,
JAXP 1.1 支持SAX2.
三、XML的解析
我们从创建DefaultHandler的一个子类开始:
import org.xml.sax.helpers.DefaultHandler;
public class MySAXApp extends DefaultHandler
{
public MySAXApp ()
{
super();
}
}
因为这是一个Java程序,我们将会创建一个Main方法来调用XMLReaderFactory 的createXMLReader方法来动态地选择一个SAX
驱动。注意了,在这里,我们只用了“throws Exception”,在真正的应用程序中,将会有容错处理:
public static void main (String args[])
throws Exception
{
XMLReader xr = XMLReaderFactory.createXMLReader();
}
可能在你的Java环境中还没有整合默认的编译路径(或者可能是
META-INF/services/org.xl.sax.driver
system resource),
那么你需要去为SAX driver设置 org.xml.sax.dirve 的Java 系统属性, 比如
java -Dorg.xml.sax.driver=com.example.xml.SAXDriver MySAXApp sample.xml 下面是击中SAX2的驱动,它们的class names和相关信息如下:
Class Name | Notes |
---|---|
gnu.xml.aelfred2.SAXDriver | Lightweight non-validating parser; Free Software |
gnu.xml.aelfred2.XmlReader | Optionally validates; Free Software |
oracle.xml.parser.v2.SAXParser | Optionally validates; proprietary |
org.apache.crimson.parser.XMLReaderImpl | Optionally validates; used in JDK 1.4; Open Source |
org.apache.xerces.parsers.SAXParser | Optionally validates; Open Source |
或者,如果你不想在你的程序中使用指定的Sax drive, 你也可以直接用它的构造器。我们假设你的XML解析器叫做com.example.xml.SAXDriver,
这实际上可能不存在。你必须知道要知道用于解析的那个真正driver的名字,这样,可能进行如下操作:
public static void main (String args[])
throws Exception
{
XMLReader xr = new com.example.xml.SAXDriver();
}
我们用xr这个对象去解析XML文档,但是,我们必须先去注册一个事件handlers,以便于解析器用来report信息,从XMLReader中调用setConentHandler 和
setErrorHandler方法,在一个实际程序的应用中,这个hanlders通常要做成一个独立的对象,但是在这里,只是为了演示,我们把handlers绑定
在了最顶层的类中(即main方法所在的类),这样,我们必须去实例化这个顶层类并且为XML Reader这个解析器注册hanlders:
public static void main (String args[])
throws Exception
{
XMLReader xr = XMLReaderFactory.createXMLReader();
MySAXApp handler = new MySAXApp();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
}
上面的代码创建了MySAXApp的一个实例去接受XML解析事件,并且把常规的content 事件和error 事件处理器注册到这个解析器上。
现在,就让我们来开始解析吧。
public static void main (String args[])
throws Exception
{
XMLReader xr = XMLReaderFactory.createXMLReader();
MySAXApp handler = new MySAXApp();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
// Parse each file provided on the
// command line.
for (int i = 0; i < args.length; i++) {
FileReader r = new FileReader(args[i]);
xr.parse(new InputSource(r));
}
}
值得注意的是,每一个reader必须被包装成一个inputSource而被解析。以下是到目前为止这个demo的整个代码:
import java.io.FileReader;
import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;
public class MySAXApp extends DefaultHandler
{
public static void main (String args[])
throws Exception
{
XMLReader xr = XMLReaderFactory.createXMLReader();
MySAXApp handler = new MySAXApp();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
// Parse each file provided on the
// command line.
for (int i = 0; i < args.length; i++) {
FileReader r = new FileReader(args[i]);
xr.parse(new InputSource(r));
}
}
public MySAXApp ()
{
super();
}
}
你可以编译上述代码并且运行它。(需要确保的一点是,你因该在org.xml.sax.dirver property 文件中定义这个Sax driver class),如果
被解析的文档不是畸形的,这个程序将啥也不会出现(畸形的xml的话,会抛xml结构异常信息)。这是因为你还没为这个应用程序set up处理事件。
处理事件
当你开始去为XML解析实现响应方法时,这就会变得越来越有趣了。其中最重要的响应事件某过于文档的开始和结束,元素的开始和结束,字符数据
的处理。
为了找出document的start和end,这个应用程序必须实现startDocument和endDocument这两个方法:
public void startDocument ()
{
System.out.println("Start document");
}
public void endDocument ()
{
System.out.println("End document");
}
这个start/endDocument 的event handler没有参数。当Sax driver一旦到达document的开头的时候,它会调用startDocument方法,一但到达结尾时,它会调用endDocument方法(即使这个文档存在一些错误)。
这个demo只是简单地输出了一些消息,但是,你的程序也可以在这个handler里增加任何你想要的代码。通常地,这些被增加的代码回去做
下面的几件事情:在momery中生成一些dom 树,输出,整合到database中,或者从XML stream中提取信息。
Sax driver将会通过同document一样的方式标志一个元素的start和end,这两个方法中会用一些参数被传递过来:
public void startElement (String uri, String name,
String qName, Attributes atts)
{
if ("".equals (uri))
System.out.println("Start element: " + qName);
else
System.out.println("Start element: {" + uri + "}" + name);
}
public void endElement (String uri, String name, String qName)
{
if ("".equals (uri))
System.out.println("End element: " + qName);
else
System.out.println("End element: {" + uri + "}" + name);
}
这个方法每次在遇到element start和end的时候,如果这个元素的namespace存在,它将会输出namespace和localname如果不存在namespace,
它将会输出它将会输出qName(qualified Name, 参考qName).
最后,SAX2通过characters这个方法显示一些常规的字符串数据。在下面的实现中,将会输出所有的字符数据(标签的文本数据)。代码可能有一点长
因为要编译一些特殊的字符:
public void characters (char ch[], int start, int length)
{
System.out.print("Characters: \"");
for (int i = start; i < start + length; i++) {
switch (ch[i]) {
case '\\':
System.out.print("\\\\");
break;
case '"':
System.out.print("\\\"");
break;
case '\n':
System.out.print("\\n");
break;
case '\r':
System.out.print("\\r");
break;
case '\t':
System.out.print("\\t");
break;
default:
System.out.print(ch[i]);
break;
}
}
System.out.print("\"\n");
}
注意,因为SAX driver可以随意获取它想要的任意字符数据块,所以你不可能仅通过一个简单的characters事件就能计算出这个element的所有字符信息。
一下是关于characters的一个具体例子:
package com.aug.desmond.sax.start.test;
import java.io.FileReader;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import static com.aug.desmond.util.Print.*;
public class MySAXApp extends DefaultHandler {
public static void main(String[] args) throws Exception{
XMLReader xr = XMLReaderFactory.createXMLReader();
MySAXApp handler = new MySAXApp();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
for(int i=0; i<args.length; i++) {
FileReader r = new FileReader(args[i]);
xr.parse(new InputSource(r));
}
}
@Override
public void characters(char ch[], int start, int length) throws SAXException {
print(new String(ch, start, length));
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
print("[end element] "+uri+","+localName+","+qName);
}
@Override
public void startElement(String uri, String localName,
String qName, Attributes attributes) throws SAXException {
print("[start element] "+uri+","+localName+","+qName);
}
public MySAXApp() {
super();
}
}
XML文件:
<?xml version="1.0" encoding="UTF-8" ?>
<root>
<head/>
<body>world</body>out_of_tag_after_body
<Chinese>China</Chinese>
</root>
输出的结果为:
[start element] ,root,root
[start element] ,head,head
[end element] ,head,head
[start element] ,body,body
world
[end element] ,body,body
out_of_tag_after_body
[start element] ,Chinese,Chinese
China
[end element] ,Chinese,Chinese
[end element] ,root,root
可以看出,
out_of_tag_after_body 在元素body 和Chinese之间,它也能够被Characters方法读出来,也就是说,Characters方法读的是tag之外的所有字符数据,
比如<a/><b/> 元素a,b之间即使没有数据,Characters也回去读它,只不过是空字符串而已。
Sax2的应用示例
下面是这个demo的全部代码:
import java.io.FileReader;
import org.xml.sax.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;
public class MySAXApp extends DefaultHandler
{
public static void main (String args[])
throws Exception
{
XMLReader xr = XMLReaderFactory.createXMLReader();
MySAXApp handler = new MySAXApp();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
// Parse each file provided on the
// command line.
for (int i = 0; i < args.length; i++) {
FileReader r = new FileReader(args[i]);
xr.parse(new InputSource(r));
}
}
public MySAXApp ()
{
super();
}
// Event handlers.
public void startDocument ()
{
System.out.println("Start document");
}
public void endDocument ()
{
System.out.println("End document");
}
public void startElement (String uri, String name,
String qName, Attributes atts)
{
if ("".equals (uri))
System.out.println("Start element: " + qName);
else
System.out.println("Start element: {" + uri + "}" + name);
}
public void endElement (String uri, String name, String qName)
{
if ("".equals (uri))
System.out.println("End element: " + qName);
else
System.out.println("End element: {" + uri + "}" + name);
}
public void characters (char ch[], int start, int length)
{
System.out.print("Characters: \"");
for (int i = start; i < start + length; i++) {
switch (ch[i]) {
case '\\':
System.out.print("\\\\");
break;
case '"':
System.out.print("\\\"");
break;
case '\n':
System.out.print("\\n");
break;
case '\r':
System.out.print("\\r");
break;
case '\t':
System.out.print("\\t");
break;
default:
System.out.print(ch[i]);
break;
}
}
System.out.print("\"\n");
}
}
输出
用到了如下的XML:
?xml version="1.0"?>
<poem xmlns="http://www.megginson.com/ns/exp/poetry">
<title>Roses are Red</title>
<l>Roses are red,</l>
<l>Violets are blue;</l>
<l>Sugar is sweet,</l>
<l>And I love you.</l>
</poem>
如果这个文档的名字叫做roses.xml并且在你的classpath中有一个叫 com.example.xml.SAXDriver(这个driver实际上并不存在)的SAX2 driver,你可以通过如下的方式去
运行它:
java -Dorg.xml.sax.driver=com.example.xml.SAXDriver MySAXApp roses.xml
输出结果如下:
Start document
Start element: {http://www.megginson.com/ns/exp/poetry}poem
Characters: "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}title
Characters: "Roses are Red"
End element: {http://www.megginson.com/ns/exp/poetry}title
Characters: "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters: "Roses are red,"
End element: {http://www.megginson.com/ns/exp/poetry}l
Characters: "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters: "Violets are blue;"
End element: {http://www.megginson.com/ns/exp/poetry}l
Characters: "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters: "Sugar is sweet,"
End element: {http://www.megginson.com/ns/exp/poetry}l
Characters: "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters: "And I love you."
End element: {http://www.megginson.com/ns/exp/poetry}l
Characters: "\n"
End element: {http://www.megginson.com/ns/exp/poetry}poem
End document
如果这个roses.xml没有包含 xmlns="http://www.megginson.com/ns/exp/poetry" 这个名空间属性去申明所有的元素将都在这个名空间下,那么,就会有以下形式的输出:
Start document
Start element: poem
Characters: "\n"
Start element: title
Characters: "Roses are Red"
End element: title
Characters: "\n"
Start element: l
Characters: "Roses are red,"
End element: l
Characters: "\n"
Start element: l
Characters: "Violets are blue;"
End element: l
Characters: "\n"
Start element: l
Characters: "Sugar is sweet,"
End element: l
Characters: "\n"
Start element: l
Characters: "And I love you."
End element: l
Characters: "\n"
End element: poem
End document
你可能用到这两种类型的document:一种用了namespace,另一种没有。对于element(或attributes),也存在同样的情况。因此,要具体情况
具体分析以便写出合适的code。
Element 的Attributes
element的attributes也可以有namespace,用法和element的一样。下面是一个demo:
XML:
<?xml version="1.0" encoding="UTF-8" ?>
<root xmlns="http://www.desmond.com" xmlns:attr="https://www.presley.com">
<head hd="hd" isHead="true"/>
<body attr:bd="bd">world</body>
<Chinese cn="cn" zone="8">China</Chinese>
</root>
Code:
package com.aug.desmond.sax.start.test;
import java.io.FileReader;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import static com.aug.desmond.util.Print.*;
public class MySAXApp extends DefaultHandler {
public static void main(String[] args) throws Exception{
XMLReader xr = XMLReaderFactory.createXMLReader();
MySAXApp handler = new MySAXApp();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
for(int i=0; i<args.length; i++) {
FileReader r = new FileReader(args[i]);
xr.parse(new InputSource(r));
}
}
@Override
public void startElement(String uri, String localName,
String qName, Attributes attributes) throws SAXException {
print("[element] uri="+uri+", localName="+localName);
print("length of attributes=" + attributes.getLength());
// get info of each attribute
for(int i = 0; i < attributes.getLength(); i++) {
System.out.print("[uri="+ attributes.getURI(i)
+ ",qName=" + attributes.getQName(i)
+ ",localName=" + attributes.getLocalName(i)
+ ", type=" + attributes.getType(i)
+ ",value=" + attributes.getValue(i) + "]");
}
// get attribute by qName
print("zone="+attributes.getValue("zone"));
print("bd="+attributes.getValue("attr:bd"));
// get attribute by uri, localName
print("bd="+attributes.getValue("https://www.presley.com", "bd"));
print("*************************");
}
public MySAXApp() {
super();
}
}
Output:
[element] uri=http://www.desmond.com, localName=root
length of attributes=0
zone=null
bd=null
bd=null
*************************
[element] uri=http://www.desmond.com, localName=head
length of attributes=2
[uri=,qName=hd,localName=hd, type=CDATA,value=hd][uri=,qName=isHead,localName=isHead, type=CDATA,value=true]zone=null
bd=null
bd=null
*************************
[element] uri=http://www.desmond.com, localName=body
length of attributes=1
[uri=https://www.presley.com,qName=attr:bd,localName=bd, type=CDATA,value=bd]zone=null
bd=bd
bd=bd
*************************
[element] uri=http://www.desmond.com, localName=Chinese
length of attributes=2
[uri=,qName=cn,localName=cn, type=CDATA,value=cn][uri=,qName=zone,localName=zone, type=CDATA,value=8]zone=8
bd=null
bd=null
*************************
来自: http://www.saxproject.org/quickstart.html